Create enum for different types of fixed permissions am: 137be684ef

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

Change-Id: Ic059cd5698a097f2137125f78f2e42ea95c8ab72
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index b2513e9..254e3c5 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -2989,24 +2989,6 @@
                                android.hardware.camera2.CaptureRequest#controlExtendedSceneMode" />
         </activity>
 
-        <activity android:name=".camera.its.CameraMuteToggleActivity"
-                 android:label="@string/camera_hw_toggle_test"
-                 android:exported="true"
-                 android:screenOrientation="landscape">
-            <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_camera" />
-            <meta-data android:name="test_required_configs" android:value="config_has_camera_toggle"/>
-            <meta-data android:name="test_required_features" android:value="android.hardware.camera.any"/>
-            <meta-data android:name="test_excluded_features"
-                       android:value="android.hardware.type.automotive"/>
-            <meta-data android:name="display_mode"
-                       android:value="single_display_mode" />
-            <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
-        </activity>
-
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
                 android:exported="true"
@@ -5708,20 +5690,6 @@
                     android.hardware.usb.UsbManager#requestPermission"/>
         </activity>
 
-        <activity android:name=".audio.AudioMicrophoneMuteToggleActivity"
-                android:label="@string/audio_mic_toggle_test"
-                android:exported="true"
-                android:screenOrientation="locked">
-            <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_audio" />
-            <meta-data android:name="test_required_configs" android:value="config_has_mic_toggle"/>
-            <meta-data android:name="display_mode" android:value="multi_display_mode" />
-            <meta-data android:name="CddTest" android:value="9.8.13/C-1-3" />
-        </activity>
-
         <service android:name=".tv.MockTvInputService"
                 android:exported="true"
             android:permission="android.permission.BIND_TV_INPUT">
diff --git a/apps/CtsVerifier/res/layout/cam_hw_toggle.xml b/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
deleted file mode 100644
index 95aced3..0000000
--- a/apps/CtsVerifier/res/layout/cam_hw_toggle.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?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.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              style="@style/RootLayoutPadding">
-
-<LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      android:orientation="vertical">
-
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="fill_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1">
-
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="0dp"
-            android:layout_height="fill_parent"
-            android:layout_weight="3">
-
-            <TextureView
-                android:id="@+id/preview_view"
-                android:layout_height="0dp"
-                android:layout_width="fill_parent"
-                android:layout_weight="3" />
-            <TextView
-                android:id="@+id/preview_label"
-                android:layout_height="wrap_content"
-                android:layout_width="fill_parent"
-                android:padding="2dp"
-                android:textSize="16sp"
-                android:gravity="center" />
-
-        </LinearLayout>
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="0dp"
-            android:layout_height="fill_parent"
-            android:layout_weight="3">
-
-            <ImageView
-                android:id="@+id/image_view"
-                android:layout_height="0dp"
-                android:layout_width="fill_parent"
-                android:layout_weight="3" />
-            <TextView
-                android:id="@+id/image_label"
-                android:layout_height="wrap_content"
-                android:layout_width="fill_parent"
-                android:padding="2dp"
-                android:textSize="16sp"
-                android:gravity="center" />
-
-        </LinearLayout>
-
-        <LinearLayout
-            android:orientation="vertical"
-            android:layout_width="0dp"
-            android:layout_height="fill_parent"
-            android:layout_weight="3"
-            android:gravity="bottom">
-
-            <TextView
-                android:id="@+id/instruction_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/camera_hw_toggle_test_instruction" />
-            <Button
-                android:id="@+id/take_picture_button"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:text="@string/co_photo_button_caption" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-    <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
-</ScrollView>
diff --git a/apps/CtsVerifier/res/layout/mic_hw_toggle.xml b/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
deleted file mode 100644
index a17abd4..0000000
--- a/apps/CtsVerifier/res/layout/mic_hw_toggle.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              style="@style/RootLayoutPadding">
-
-<LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      android:orientation="vertical">
-
-  <TextView
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:id="@+id/info_text"/>
-
-  <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="3"
-      android:orientation="horizontal">
-    <Button
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="5"
-        android:text="@string/hifi_ultrasound_test_record"
-        android:id="@+id/recorder_button"/>
-  </LinearLayout>
-
-      <include layout="@layout/pass_fail_buttons" />
-      </LinearLayout>
-</ScrollView>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 131228a..b49337c 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -6674,41 +6674,4 @@
     <string name="report_distance_range_5m_gt_90p">Measurement Range at 5m (90th percentile)</string>
     <string name="nan_precision">Nan Precision Test</string>
     <string name="wifi_nan">WiFi NAN</string>
-
-    <!-- Strings for AudioMicrophoneMuteToggleActivity -->
-    <string name="audio_mic_toggle_test">Audio Microphone Hardware Toggle Mute Test</string>
-    <string name="audio_mic_toggle_test_info">
-        This test verifies that devices which implement microphone hardware privacy toggles enforce sensor privacy when toggles are enabled.
-        \nTo pass the test:
-        \n  - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The audio stream should be muted</a>.
-        \n  - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
-    </string>
-    <string name="audio_mic_toggle_test_instruction1">
-        Mute the microphone using the hardware privacy toggle.
-        \nPress the RECORD button.
-        \nObserve a dialog with information regarding the microphone being blocked
-        \nIgnore/cancel the dialog and wait for the recording to complete.
-        \nThe pass button will be enabled if the test succeeded.</string>
-    <string name="audio_mic_toggle_test_analyzing">Analyzing, please wait...\n</string>
-
-    <!-- Strings for CameraMuteToggleActivity -->
-    <string name="camera_hw_toggle_test">Camera Hardware Toggle Mute Test</string>
-    <string name="camera_hw_toggle_test_info">
-        This test verifies that devices which implement camera hardware privacy toggles enforce sensor privacy when toggles are enabled.
-        \nTo pass the test:
-        \n  - <a href="https://source.android.com/compatibility/android-cdd#9813_sensorprivacymanager">The video stream should be muted</a>.
-        \n  - A dialog or notification should be shown that informs the user that the sensor privacy is enabled.
-    </string>
-    <string name="camera_hw_toggle_test_instruction">
-        Mute the camera using the hardware privacy toggle.
-        \nObserve a dialog with information regarding the camera being blocked.
-        \nCamera preview should show a blank feed.
-        \nPress the Take Photo button.
-        \nCaptured image should be black.
-        \nMark the test as passing if the above conditions are met.</string>
-    <string name="camera_hw_toggle_test_no_camera">
-        No available camera found.
-        \nAdd or enable a camera and re-run this test.
-    </string>
-
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 9393283..7bf6b5d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -19,14 +19,12 @@
 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
 import static com.android.cts.verifier.TestListActivity.sInitialLaunch;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
 import android.os.Bundle;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -144,10 +142,6 @@
 
     private static final String CONFIG_QUICK_SETTINGS_SUPPORTED = "config_quick_settings_supported";
 
-    private static final String CONFIG_HAS_MIC_TOGGLE = "config_has_mic_toggle";
-
-    private static final String CONFIG_HAS_CAMERA_TOGGLE = "config_has_camera_toggle";
-
     /** The config to represent that a test is only needed to run in the main display mode
      * (i.e. unfolded) */
     private static final String SINGLE_DISPLAY_MODE = "single_display_mode";
@@ -486,10 +480,6 @@
                             return false;
                         }
                         break;
-                    case CONFIG_HAS_MIC_TOGGLE:
-                        return isHardwareToggleSupported(SensorPrivacyManager.Sensors.MICROPHONE);
-                    case CONFIG_HAS_CAMERA_TOGGLE:
-                        return isHardwareToggleSupported(SensorPrivacyManager.Sensors.CAMERA);
                     default:
                         break;
                 }
@@ -591,16 +581,4 @@
             super.loadTestResults();
         }
     }
-
-    @SuppressLint("NewApi")
-    private boolean isHardwareToggleSupported(final int sensorType) {
-        boolean isToggleSupported = false;
-        SensorPrivacyManager sensorPrivacyManager = mContext.getSystemService(
-                SensorPrivacyManager.class);
-        if (sensorPrivacyManager != null) {
-            isToggleSupported = sensorPrivacyManager.supportsSensorToggle(
-                    SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensorType);
-        }
-        return isToggleSupported;
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
index 3f1266e..3c1b8a1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioAEC.java
@@ -17,11 +17,11 @@
 package com.android.cts.verifier.audio;
 
 import android.content.Context;
-import android.media.audiofx.AcousticEchoCanceler;
 import android.media.AudioManager;
-import android.media.AudioTrack;
 import android.media.AudioRecord;
+import android.media.AudioTrack;
 import android.media.MediaRecorder;
+import android.media.audiofx.AcousticEchoCanceler;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
@@ -32,71 +32,130 @@
 
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.soundio.SoundRecorderObject;
-import com.android.cts.verifier.audio.wavelib.*;
 import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+import com.android.cts.verifier.audio.wavelib.DspBufferDouble;
+import com.android.cts.verifier.audio.wavelib.DspBufferMath;
+import com.android.cts.verifier.audio.wavelib.PipeShort;
 
 public class AudioAEC extends AudioFrequencyActivity implements View.OnClickListener {
     private static final String TAG = "AudioAEC";
 
-    // Test State
+    private static final int TEST_NONE = -1;
     private static final int TEST_AEC = 0;
+    private static final int TEST_COUNT = 1;
+    private static final float MAX_VAL = (float)(1 << 15);
 
-    // UI
+    private int mCurrentTest = TEST_NONE;
     private LinearLayout mLinearLayout;
     private Button mButtonTest;
     private Button mButtonMandatoryYes;
     private Button mButtonMandatoryNo;
     private ProgressBar mProgress;
     private TextView mResultTest;
+    private boolean mTestAECPassed;
+    private SoundPlayerObject mSPlayer;
+    private SoundRecorderObject mSRecorder;
+    private AcousticEchoCanceler mAec;
 
-    // Sound IO
+    private boolean mMandatory = true;
+
     private final int mBlockSizeSamples = 4096;
     private final int mSamplingRate = 48000;
     private final int mSelectedRecordSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
 
-    private SoundPlayerObject mSPlayer;
-    private SoundRecorderObject mSRecorder;
-
-    // Test Results
-    private boolean mMandatory = true;
-    private boolean mTestAECPassed;
-
     private final int TEST_DURATION_MS = 8000;
     private final int SHOT_FREQUENCY_MS = 200;
     private final int CORRELATION_DURATION_MS = TEST_DURATION_MS - 3000;
     private final int SHOT_COUNT_CORRELATION = CORRELATION_DURATION_MS/SHOT_FREQUENCY_MS;
     private final int SHOT_COUNT = TEST_DURATION_MS/SHOT_FREQUENCY_MS;
-
     private final float MIN_RMS_DB = -60.0f; //dB
     private final float MIN_RMS_VAL = (float)Math.pow(10,(MIN_RMS_DB/20));
 
     private final double TEST_THRESHOLD_AEC_ON = 0.5;
     private final double TEST_THRESHOLD_AEC_OFF = 0.6;
-    private RmsHelper mRMSRecorder1 =
-            new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
-    private RmsHelper mRMSRecorder2 =
-            new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
+    private RmsHelper mRMSRecorder1 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
+    private RmsHelper mRMSRecorder2 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
 
-    private RmsHelper mRMSPlayer1 =
-            new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
-    private RmsHelper mRMSPlayer2 =
-            new RmsHelper(mBlockSizeSamples, SHOT_COUNT, MIN_RMS_DB, MIN_RMS_VAL);
+    private RmsHelper mRMSPlayer1 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
+    private RmsHelper mRMSPlayer2 = new RmsHelper(mBlockSizeSamples, SHOT_COUNT);
 
     private Thread mTestThread;
 
-    // ReportLog schema
-    private static final String SECTION_AEC = "aec_activity";
-    private static final String KEY_AEC_MANDATORY = "aec_mandatory";
-    private static final String KEY_AEC_MAX_WITH = "max_with_aec";
-    private static final String KEY_AEC_MAX_WITHOUT = "max_without_aec";
-    private static final String KEY_AEC_RESULT = "result_string";
+    //RMS helpers
+    public class RmsHelper {
+        private double mRmsCurrent;
+        public int mBlockSize;
+        private int mShoutCount;
+        public boolean mRunning = false;
+
+        private short[] mAudioShortArray;
+
+        private DspBufferDouble mRmsSnapshots;
+        private int mShotIndex;
+
+        public RmsHelper(int blockSize, int shotCount) {
+            mBlockSize = blockSize;
+            mShoutCount = shotCount;
+            reset();
+        }
+
+        public void reset() {
+            mAudioShortArray = new short[mBlockSize];
+            mRmsSnapshots = new DspBufferDouble(mShoutCount);
+            mShotIndex = 0;
+            mRmsCurrent = 0;
+            mRunning = false;
+        }
+
+        public void captureShot() {
+            if (mShotIndex >= 0 && mShotIndex < mRmsSnapshots.getSize()) {
+                mRmsSnapshots.setValue(mShotIndex++, mRmsCurrent);
+            }
+        }
+
+        public void setRunning(boolean running) {
+            mRunning = running;
+        }
+
+        public double getRmsCurrent() {
+            return mRmsCurrent;
+        }
+
+        public DspBufferDouble getRmsSnapshots() {
+            return mRmsSnapshots;
+        }
+
+        public boolean updateRms(PipeShort pipe, int channelCount, int channel) {
+            if (mRunning) {
+                int samplesAvailable = pipe.availableToRead();
+                while (samplesAvailable >= mBlockSize) {
+                    pipe.read(mAudioShortArray, 0, mBlockSize);
+
+                    double rmsTempSum = 0;
+                    int count = 0;
+                    for (int i = channel; i < mBlockSize; i += channelCount) {
+                        float value = mAudioShortArray[i] / MAX_VAL;
+
+                        rmsTempSum += value * value;
+                        count++;
+                    }
+                    float rms = count > 0 ? (float)Math.sqrt(rmsTempSum / count) : 0f;
+                    if (rms < MIN_RMS_VAL) {
+                        rms = MIN_RMS_VAL;
+                    }
+
+                    double alpha = 0.9;
+                    double total_rms = rms * alpha + mRmsCurrent * (1.0f - alpha);
+                    mRmsCurrent = total_rms;
+
+                    samplesAvailable = pipe.availableToRead();
+                }
+                return true;
+            }
+            return false;
+        }
+    }
 
     //compute Acoustic Coupling Factor
     private double computeAcousticCouplingFactor(DspBufferDouble buffRmsPlayer,
@@ -143,7 +202,7 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_aec_activity);
 
-        // "AEC Mandatory" Buttons
+        //
         mLinearLayout = (LinearLayout)findViewById(R.id.audio_aec_test_layout);
         mButtonMandatoryYes = (Button) findViewById(R.id.audio_aec_mandatory_yes);
         mButtonMandatoryYes.setOnClickListener(this);
@@ -151,15 +210,16 @@
         mButtonMandatoryNo.setOnClickListener(this);
         enableUILayout(mLinearLayout, false);
 
-        // Test Button
+        // Test
         mButtonTest = (Button) findViewById(R.id.audio_aec_button_test);
         mButtonTest.setOnClickListener(this);
         mProgress = (ProgressBar) findViewById(R.id.audio_aec_test_progress_bar);
         mResultTest = (TextView) findViewById(R.id.audio_aec_test_result);
 
-        showProgressIndicator(false);
+        showView(mProgress, false);
 
         mSPlayer = new SoundPlayerObject(false, mBlockSizeSamples) {
+
             @Override
             public void periodicNotification(AudioTrack track) {
                 int channelCount = getChannelCount();
@@ -179,11 +239,12 @@
 
         setPassFailButtonClickListeners();
         getPassButton().setEnabled(false);
-        setInfoResources(R.string.audio_aec_test, R.string.audio_aec_info, -1);
+        setInfoResources(R.string.audio_aec_test,
+                R.string.audio_aec_info, -1);
     }
 
-    private void showProgressIndicator(boolean show) {
-        mProgress.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+    private void showView(View v, boolean show) {
+        v.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
     }
 
     @Override
@@ -213,11 +274,7 @@
             Log.v(TAG,"test Thread already running.");
             return;
         }
-
         mTestThread = new Thread(new AudioTestRunner(TAG, TEST_AEC, mMessageHandler) {
-            // AcousticEchoCanceler
-            private AcousticEchoCanceler mAec;
-
             public void run() {
                 super.run();
 
@@ -433,39 +490,28 @@
                                   String msg) {
 
         CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(KEY_AEC_MANDATORY,
+        reportLog.addValue("AEC_mandatory",
                 aecMandatory,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
 
-        reportLog.addValue(KEY_AEC_MAX_WITH,
+        reportLog.addValue("max_with_AEC",
                 maxAEC,
                 ResultType.LOWER_BETTER,
                 ResultUnit.SCORE);
 
-        reportLog.addValue(KEY_AEC_MAX_WITHOUT,
+        reportLog.addValue("max_without_AEC",
                 maxNoAEC,
                 ResultType.HIGHER_BETTER,
                 ResultUnit.SCORE);
 
-        reportLog.addValue(KEY_AEC_RESULT,
+        reportLog.addValue("result_string",
                 msg,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
     }
 
-    //
-    // PassFailButtons Overrides
-    //
-    @Override
-    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
-
-    @Override
-    public final String getReportSectionName() {
-        return setTestNameSuffix(sCurrentDisplayMode, SECTION_AEC);
-    }
-
-    @Override
+    @Override // PassFailButtons
     public void recordTestResults() {
         getReportLog().submit();
     }
@@ -477,7 +523,7 @@
         public void testStarted(int testId, String str) {
             super.testStarted(testId, str);
             Log.v(TAG, "Test Started! " + testId + " str:"+str);
-            showProgressIndicator(true);
+            showView(mProgress, true);
             mTestAECPassed = false;
             getPassButton().setEnabled(false);
             mResultTest.setText("test in progress..");
@@ -494,7 +540,7 @@
         public void testEndedOk(int testId, String str) {
             super.testEndedOk(testId, str);
             Log.v(TAG, "Test EndedOk. " + testId + " str:"+str);
-            showProgressIndicator(false);
+            showView(mProgress, false);
             mResultTest.setText("test completed. " + str);
             if (!isReportLogOkToPass()) {
                 mResultTest.setText(getResources().getString(R.string.audio_general_reportlogtest));
@@ -507,7 +553,7 @@
         public void testEndedError(int testId, String str) {
             super.testEndedError(testId, str);
             Log.v(TAG, "Test EndedError. " + testId + " str:"+str);
-            showProgressIndicator(false);
+            showView(mProgress, false);
             mResultTest.setText("test failed. " + str);
         }
     };
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
index 9b24039..582ea0c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
@@ -32,7 +32,6 @@
 
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
 import com.android.cts.verifier.CtsVerifierReportLog;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
index e066943..a83709d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyMicActivity.java
@@ -40,9 +40,6 @@
 import com.android.cts.verifier.audio.wavelib.DspFftServer;
 import com.android.cts.verifier.audio.wavelib.DspWindow;
 import com.android.cts.verifier.audio.wavelib.PipeShort;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.wavelib.*;
-
 import com.android.cts.verifier.audio.wavelib.VectorAverage;
 
 /**
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
index 0e63b6f..301eb9c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencySpeakerActivity.java
@@ -32,7 +32,6 @@
 
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
 import com.android.cts.verifier.CtsVerifierReportLog;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
index 21de117..3819be2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyUnprocessedActivity.java
@@ -34,7 +34,6 @@
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
 import com.android.cts.verifier.CtsVerifierReportLog;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
index 442f626..23a016a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyVoiceRecognitionActivity.java
@@ -30,8 +30,6 @@
 
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.soundio.SoundPlayerObject;
-import com.android.cts.verifier.audio.soundio.SoundRecorderObject;
 import com.android.cts.verifier.CtsVerifierReportLog;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.audio.wavelib.DspBufferComplex;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
deleted file mode 100644
index 1e250ef..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioMicrophoneMuteToggleActivity.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * 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.audio;
-
-import android.media.MediaRecorder;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.text.method.ScrollingMovementMethod;
-import android.util.Log;
-import android.view.View;
-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.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
-/**
- * Test for manual verification of microphone privacy hardware switches
- */
-public class AudioMicrophoneMuteToggleActivity extends PassFailButtons.Activity {
-
-    public enum Status {
-        START, RECORDING, DONE, PLAYER
-    }
-
-    private static final String TAG = "AudioMicrophoneMuteToggleActivity";
-
-    private Status mStatus = Status.START;
-    private TextView mInfoText;
-    private Button mRecorderButton;
-
-    private int mAudioSource = -1;
-    private int mRecordRate = 0;
-
-    // keys for report log
-    private static final String KEY_REC_RATE = "rec_rate";
-    private static final String KEY_AUDIO_SOURCE = "audio_source";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.mic_hw_toggle);
-        setInfoResources(R.string.audio_mic_toggle_test, R.string.audio_mic_toggle_test_info, -1);
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-
-        mInfoText = findViewById(R.id.info_text);
-        mInfoText.setMovementMethod(new ScrollingMovementMethod());
-        mInfoText.setText(R.string.audio_mic_toggle_test_instruction1);
-
-        mRecorderButton = findViewById(R.id.recorder_button);
-        mRecorderButton.setEnabled(true);
-
-        final AudioRecordHelper audioRecorder = AudioRecordHelper.getInstance();
-        mRecordRate = audioRecorder.getSampleRate();
-
-        mRecorderButton.setOnClickListener(new View.OnClickListener() {
-            private WavAnalyzerTask mWavAnalyzerTask = null;
-
-            private void stopRecording() {
-                audioRecorder.stop();
-                mInfoText.append(getString(R.string.audio_mic_toggle_test_analyzing));
-                mWavAnalyzerTask = new WavAnalyzerTask(audioRecorder.getByte());
-                mWavAnalyzerTask.execute();
-                mStatus = Status.DONE;
-            }
-
-            @Override
-            public void onClick(View v) {
-                switch (mStatus) {
-                    case START:
-                        mInfoText.append("Recording at " + mRecordRate + "Hz using ");
-                        mAudioSource = audioRecorder.getAudioSource();
-                        switch (mAudioSource) {
-                            case MediaRecorder.AudioSource.MIC:
-                                mInfoText.append("MIC");
-                                break;
-                            case MediaRecorder.AudioSource.VOICE_RECOGNITION:
-                                mInfoText.append("VOICE_RECOGNITION");
-                                break;
-                            default:
-                                mInfoText.append("UNEXPECTED " + mAudioSource);
-                                break;
-                        }
-                        mInfoText.append("\n");
-                        mStatus = Status.RECORDING;
-
-                        mRecorderButton.setEnabled(false);
-                        audioRecorder.start();
-
-                        final View finalV = v;
-                        new Thread() {
-                            @Override
-                            public void run() {
-                                double recordingDuration_millis = (1000 * (2.5
-                                        + AudioCommon.PREFIX_LENGTH_S
-                                        + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
-                                        + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
-                                        + AudioCommon.PIP_NUM * (AudioCommon.PIP_DURATION_S
-                                        + AudioCommon.PAUSE_DURATION_S)
-                                        * AudioCommon.REPETITIONS));
-                                Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
-                                try {
-                                    Thread.sleep((long) recordingDuration_millis);
-                                } catch (InterruptedException e) {
-                                    throw new RuntimeException(e);
-                                }
-                                runOnUiThread(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        stopRecording();
-                                    }
-                                });
-                            }
-                        }.start();
-
-                        break;
-
-                    default:
-                        break;
-                }
-            }
-        });
-
-    }
-
-    @Override
-    public void recordTestResults() {
-        CtsVerifierReportLog reportLog = getReportLog();
-
-        reportLog.addValue(
-                KEY_REC_RATE,
-                mRecordRate,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_AUDIO_SOURCE,
-                mAudioSource,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.submit();
-    }
-
-    /**
-     * AsyncTask class for the analyzing.
-     */
-    private class WavAnalyzerTask extends AsyncTask<Void, String, String>
-            implements WavAnalyzer.Listener {
-
-        private static final String TAG = "WavAnalyzerTask";
-        private final WavAnalyzer mWavAnalyzer;
-
-        public WavAnalyzerTask(byte[] recording) {
-            mWavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
-                    WavAnalyzerTask.this);
-        }
-
-        @Override
-        protected String doInBackground(Void... params) {
-            boolean result = mWavAnalyzer.doWork();
-            if (result) {
-                return getString(R.string.pass_button_text);
-            }
-            return getString(R.string.fail_button_text);
-        }
-
-        @Override
-        protected void onPostExecute(String result) {
-            if (mWavAnalyzer.isSilence()) {
-                mInfoText.append(getString(R.string.passed));
-                getPassButton().setEnabled(true);
-            } else {
-                mInfoText.append(getString(R.string.failed));
-            }
-        }
-
-        @Override
-        protected void onProgressUpdate(String... values) {
-            for (String message : values) {
-                Log.d(TAG, message);
-            }
-        }
-
-        @Override
-        public void sendMessage(String message) {
-            publishProgress(message);
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
similarity index 90%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
index ba5e39b2..df7460a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioCommon.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/Common.java
@@ -1,18 +1,15 @@
-package com.android.cts.verifier.audio.audiolib;
+package com.android.cts.verifier.audio;
 
 import android.media.AudioManager;
 import android.media.AudioTrack;
 
-import com.android.cts.verifier.audio.AudioRecordHelper;
-import com.android.cts.verifier.audio.Util;
-
 import java.util.ArrayList;
 import java.util.Random;
 
 /**
  * This class stores common constants and methods.
  */
-public class AudioCommon {
+public class Common {
 
   public static final int RECORDING_SAMPLE_RATE_HZ
       = AudioRecordHelper.getInstance().getSampleRate();
@@ -98,7 +95,7 @@
   private static double[] frequencies() {
     double[] originalFrequencies = originalFrequencies();
 
-    double[] randomFrequencies = new double[AudioCommon.REPETITIONS * originalFrequencies.length];
+    double[] randomFrequencies = new double[Common.REPETITIONS * originalFrequencies.length];
     for (int i = 0; i < REPETITIONS * originalFrequencies.length; i++) {
       randomFrequencies[i] = originalFrequencies[ORDER[i] % originalFrequencies.length];
     }
@@ -111,13 +108,13 @@
    */
   private static double[] originalFrequencies() {
     ArrayList<Double> frequencies = new ArrayList<Double>();
-    double frequency = AudioCommon.MIN_FREQUENCY_HZ;
-    while (frequency <= AudioCommon.MAX_FREQUENCY_HZ) {
+    double frequency = Common.MIN_FREQUENCY_HZ;
+    while (frequency <= Common.MAX_FREQUENCY_HZ) {
       frequencies.add(new Double(frequency));
       if ((frequency >= 18500) && (frequency < 20000)) {
-        frequency += AudioCommon.FREQUENCY_STEP_HZ;
+        frequency += Common.FREQUENCY_STEP_HZ;
       } else {
-        frequency += AudioCommon.FREQUENCY_STEP_HZ * 10;
+        frequency += Common.FREQUENCY_STEP_HZ * 10;
       }
     }
     Double[] frequenciesArray = frequencies.toArray(new Double[frequencies.size()]);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index 232e14d..cac4659 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -37,10 +37,6 @@
 import android.widget.TextView;
 import java.util.Arrays;
 
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
 import com.androidplot.xy.PointLabelFormatter;
 import com.androidplot.xy.LineAndPointFormatter;
 import com.androidplot.xy.SimpleXYSeries;
@@ -184,12 +180,11 @@
               @Override
               public void run() {
                 Double recordingDuration_millis = new Double(1000 * (2.5
-                    + AudioCommon.PREFIX_LENGTH_S
-                    + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
-                    + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
-                    + AudioCommon.PIP_NUM
-                        * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S)
-                    * AudioCommon.REPETITIONS));
+                    + Common.PREFIX_LENGTH_S
+                    + Common.PAUSE_BEFORE_PREFIX_DURATION_S
+                    + Common.PAUSE_AFTER_PREFIX_DURATION_S
+                    + Common.PIP_NUM * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S)
+                    * Common.REPETITIONS));
                 Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
                 try {
                   Thread.sleep(recordingDuration_millis.intValue());
@@ -266,17 +261,17 @@
     XYPlot plot = (XYPlot) popupView.findViewById(R.id.responseChart);
     plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 2000);
 
-    Double[] frequencies = new Double[AudioCommon.PIP_NUM];
-    for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
-      frequencies[i] = new Double(AudioCommon.FREQUENCIES_ORIGINAL[i]);
+    Double[] frequencies = new Double[Common.PIP_NUM];
+    for (int i = 0; i < Common.PIP_NUM; i++) {
+      frequencies[i] = new Double(Common.FREQUENCIES_ORIGINAL[i]);
     }
 
     if (wavAnalyzerTask != null) {
 
       double[][] power = wavAnalyzerTask.getPower();
-      for(int i = 0; i < AudioCommon.REPETITIONS; i++) {
-        Double[] powerWrap = new Double[AudioCommon.PIP_NUM];
-        for (int j = 0; j < AudioCommon.PIP_NUM; j++) {
+      for(int i = 0; i < Common.REPETITIONS; i++) {
+        Double[] powerWrap = new Double[Common.PIP_NUM];
+        for (int j = 0; j < Common.PIP_NUM; j++) {
           powerWrap[j] = new Double(10 * Math.log10(power[j][i]));
         }
         XYSeries series = new SimpleXYSeries(
@@ -291,8 +286,8 @@
       }
 
       double[] noiseDB = wavAnalyzerTask.getNoiseDB();
-      Double[] noiseDBWrap = new Double[AudioCommon.PIP_NUM];
-      for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+      Double[] noiseDBWrap = new Double[Common.PIP_NUM];
+      for (int i = 0; i < Common.PIP_NUM; i++) {
         noiseDBWrap[i] = new Double(noiseDB[i]);
       }
 
@@ -307,8 +302,8 @@
       plot.addSeries(noiseSeries, noiseSeriesFormat);
 
       double[] dB = wavAnalyzerTask.getDB();
-      Double[] dBWrap = new Double[AudioCommon.PIP_NUM];
-      for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+      Double[] dBWrap = new Double[Common.PIP_NUM];
+      for (int i = 0; i < Common.PIP_NUM; i++) {
         dBWrap[i] = new Double(dB[i]);
       }
 
@@ -322,7 +317,7 @@
           R.xml.ultrasound_line_formatter_median);
       plot.addSeries(series, seriesFormat);
 
-      Double[] passX = new Double[] {AudioCommon.MIN_FREQUENCY_HZ, AudioCommon.MAX_FREQUENCY_HZ};
+      Double[] passX = new Double[] {Common.MIN_FREQUENCY_HZ, Common.MAX_FREQUENCY_HZ};
       Double[] passY = new Double[] {wavAnalyzerTask.getThreshold(), wavAnalyzerTask.getThreshold()};
       XYSeries passSeries = new SimpleXYSeries(
           Arrays.asList(passX), Arrays.asList(passY), "passing");
@@ -338,7 +333,7 @@
    * Plays the generated pips.
    */
   private void play() {
-    play(SoundGenerator.getInstance().getByte(), AudioCommon.PLAYING_SAMPLE_RATE_HZ);
+    play(SoundGenerator.getInstance().getByte(), Common.PLAYING_SAMPLE_RATE_HZ);
   }
 
   /**
@@ -368,7 +363,7 @@
     WavAnalyzer wavAnalyzer;
 
     public WavAnalyzerTask(byte[] recording) {
-      wavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
+      wavAnalyzer = new WavAnalyzer(recording, Common.RECORDING_SAMPLE_RATE_HZ,
           WavAnalyzerTask.this);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
index a5a11f2..5f1be38 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundTestActivity.java
@@ -37,10 +37,6 @@
 import android.widget.TextView;
 import java.util.Arrays;
 
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.soundio.SoundGenerator;
-import com.android.cts.verifier.audio.wavelib.WavAnalyzer;
-
 import com.androidplot.xy.PointLabelFormatter;
 import com.androidplot.xy.LineAndPointFormatter;
 import com.androidplot.xy.SimpleXYSeries;
@@ -164,11 +160,11 @@
               @Override
               public void run() {
                 Double recordingDuration_millis = new Double(1000 * (2.5
-                    + AudioCommon.PREFIX_LENGTH_S
-                    + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
-                    + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S
-                    + AudioCommon.PIP_NUM * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S)
-                    * AudioCommon.REPETITIONS));
+                    + Common.PREFIX_LENGTH_S
+                    + Common.PAUSE_BEFORE_PREFIX_DURATION_S
+                    + Common.PAUSE_AFTER_PREFIX_DURATION_S
+                    + Common.PIP_NUM * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S)
+                    * Common.REPETITIONS));
                 Log.d(TAG, "Recording for " + recordingDuration_millis + "ms");
                 try {
                   Thread.sleep(recordingDuration_millis.intValue());
@@ -224,18 +220,18 @@
     XYPlot plot = (XYPlot) popupView.findViewById(R.id.responseChart);
     plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 2000);
 
-    Double[] frequencies = new Double[AudioCommon.PIP_NUM];
-    for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
-      frequencies[i] = new Double(AudioCommon.FREQUENCIES_ORIGINAL[i]);
+    Double[] frequencies = new Double[Common.PIP_NUM];
+    for (int i = 0; i < Common.PIP_NUM; i++) {
+      frequencies[i] = new Double(Common.FREQUENCIES_ORIGINAL[i]);
     }
 
     if (wavAnalyzerTask != null && wavAnalyzerTask.getPower() != null &&
         wavAnalyzerTask.getNoiseDB() != null && wavAnalyzerTask.getDB() != null) {
 
       double[][] power = wavAnalyzerTask.getPower();
-      for(int i = 0; i < AudioCommon.REPETITIONS; i++) {
-        Double[] powerWrap = new Double[AudioCommon.PIP_NUM];
-        for (int j = 0; j < AudioCommon.PIP_NUM; j++) {
+      for(int i = 0; i < Common.REPETITIONS; i++) {
+        Double[] powerWrap = new Double[Common.PIP_NUM];
+        for (int j = 0; j < Common.PIP_NUM; j++) {
           powerWrap[j] = new Double(10 * Math.log10(power[j][i]));
         }
         XYSeries series = new SimpleXYSeries(
@@ -250,8 +246,8 @@
       }
 
       double[] noiseDB = wavAnalyzerTask.getNoiseDB();
-      Double[] noiseDBWrap = new Double[AudioCommon.PIP_NUM];
-      for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+      Double[] noiseDBWrap = new Double[Common.PIP_NUM];
+      for (int i = 0; i < Common.PIP_NUM; i++) {
         noiseDBWrap[i] = new Double(noiseDB[i]);
       }
 
@@ -266,8 +262,8 @@
       plot.addSeries(noiseSeries, noiseSeriesFormat);
 
       double[] dB = wavAnalyzerTask.getDB();
-      Double[] dBWrap = new Double[AudioCommon.PIP_NUM];
-      for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+      Double[] dBWrap = new Double[Common.PIP_NUM];
+      for (int i = 0; i < Common.PIP_NUM; i++) {
         dBWrap[i] = new Double(dB[i]);
       }
 
@@ -281,7 +277,7 @@
           R.xml.ultrasound_line_formatter_median);
       plot.addSeries(series, seriesFormat);
 
-      Double[] passX = new Double[] {AudioCommon.MIN_FREQUENCY_HZ, AudioCommon.MAX_FREQUENCY_HZ};
+      Double[] passX = new Double[] {Common.MIN_FREQUENCY_HZ, Common.MAX_FREQUENCY_HZ};
       Double[] passY = new Double[] {wavAnalyzerTask.getThreshold(), wavAnalyzerTask.getThreshold()};
       XYSeries passSeries = new SimpleXYSeries(
           Arrays.asList(passX), Arrays.asList(passY), "passing");
@@ -297,7 +293,7 @@
    * Plays the generated pips.
    */
   private void play() {
-    play(SoundGenerator.getInstance().getByte(), AudioCommon.PLAYING_SAMPLE_RATE_HZ);
+    play(SoundGenerator.getInstance().getByte(), Common.PLAYING_SAMPLE_RATE_HZ);
   }
 
   /**
@@ -327,7 +323,7 @@
     WavAnalyzer wavAnalyzer;
 
     public WavAnalyzerTask(byte[] recording) {
-      wavAnalyzer = new WavAnalyzer(recording, AudioCommon.RECORDING_SAMPLE_RATE_HZ,
+      wavAnalyzer = new WavAnalyzer(recording, Common.RECORDING_SAMPLE_RATE_HZ,
           WavAnalyzerTask.this);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java
new file mode 100644
index 0000000..0ad9371
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundGenerator.java
@@ -0,0 +1,81 @@
+package com.android.cts.verifier.audio;
+
+/**
+ * Sound generator.
+ */
+public class SoundGenerator {
+
+  private static SoundGenerator instance;
+
+  private final byte[] generatedSound;
+  private final double[] sample;
+
+  private SoundGenerator() {
+    // Initialize sample.
+    int pipNum = Common.PIP_NUM;
+    int prefixTotalLength = Util.toLength(Common.PREFIX_LENGTH_S, Common.PLAYING_SAMPLE_RATE_HZ)
+        + Util.toLength(Common.PAUSE_BEFORE_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ)
+        + Util.toLength(Common.PAUSE_AFTER_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ);
+    int repetitionLength = pipNum * Util.toLength(
+        Common.PIP_DURATION_S + Common.PAUSE_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ);
+    int sampleLength = prefixTotalLength + Common.REPETITIONS * repetitionLength;
+    sample = new double[sampleLength];
+
+    // Fill sample with prefix.
+    System.arraycopy(Common.PREFIX_FOR_PLAYER, 0, sample,
+        Util.toLength(Common.PAUSE_BEFORE_PREFIX_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ),
+        Common.PREFIX_FOR_PLAYER.length);
+
+    // Fill the sample.
+    for (int i = 0; i < pipNum * Common.REPETITIONS; i++) {
+      double[] pip = getPip(Common.WINDOW_FOR_PLAYER, Common.FREQUENCIES[i]);
+      System.arraycopy(pip, 0, sample,
+          prefixTotalLength + i * Util.toLength(
+              Common.PIP_DURATION_S + Common.PAUSE_DURATION_S, Common.PLAYING_SAMPLE_RATE_HZ),
+          pip.length);
+    }
+
+    // Convert sample to byte.
+    generatedSound = new byte[2 * sample.length];
+    int i = 0;
+    for (double dVal : sample) {
+      short val = (short) ((dVal * 32767));
+      generatedSound[i++] = (byte) (val & 0x00ff);
+      generatedSound[i++] = (byte) ((val & 0xff00) >>> 8);
+    }
+  }
+
+  public static SoundGenerator getInstance() {
+    if (instance == null) {
+      instance = new SoundGenerator();
+    }
+    return instance;
+  }
+
+  /**
+   * Gets a pip sample.
+   */
+  private static double[] getPip(double[] window, double frequency) {
+    int pipArrayLength = window.length;
+    double[] pipArray = new double[pipArrayLength];
+    double radPerSample = 2 * Math.PI / (Common.PLAYING_SAMPLE_RATE_HZ / frequency);
+    for (int i = 0; i < pipArrayLength; i++) {
+      pipArray[i] = window[i] * Math.sin(i * radPerSample);
+    }
+    return pipArray;
+  }
+
+  /**
+   * Get generated sound in byte[].
+   */
+  public byte[] getByte() {
+    return generatedSound;
+  }
+
+  /**
+   * Get sample in double[].
+   */
+  public double[] getSample() {
+    return sample;
+  }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
similarity index 99%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
index 156460b..0d93dbb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundPlayerObject.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundPlayerObject.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.verifier.audio.soundio;
+package com.android.cts.verifier.audio;
 
 import android.content.Context;
 import android.media.AudioAttributes;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
similarity index 98%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
index d83a5dd..8950ec5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundRecorderObject.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/SoundRecorderObject.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.cts.verifier.audio.soundio;
+package com.android.cts.verifier.audio;
 
 import android.media.AudioFormat;
 import android.media.AudioRecord;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
similarity index 62%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
index 038f080..b75c40b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/WavAnalyzer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/WavAnalyzer.java
@@ -1,22 +1,4 @@
-/*
- * 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.audio.wavelib;
-
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.Util;
+package com.android.cts.verifier.audio;
 
 import org.apache.commons.math.complex.Complex;
 
@@ -27,8 +9,6 @@
  * Class contains the analysis to calculate frequency response.
  */
 public class WavAnalyzer {
-  final double SILENCE_THRESHOLD = Short.MAX_VALUE / 100.0f;
-
   private final Listener listener;
   private final int sampleRate;  // Recording sampling rate.
   private double[] data;  // Whole recording data.
@@ -80,7 +60,7 @@
   /**
    * Check if the recording is clipped.
    */
-  public boolean isClipped() {
+  boolean isClipped() {
     for (int i = 1; i < data.length; i++) {
       if ((Math.abs(data[i]) >= Short.MAX_VALUE) && (Math.abs(data[i - 1]) >= Short.MAX_VALUE)) {
         listener.sendMessage("WARNING: Data is clipped."
@@ -94,11 +74,11 @@
   /**
    * Check if the result is consistant across trials.
    */
-  public boolean isConsistent() {
-    double[] coeffOfVar = new double[AudioCommon.PIP_NUM];
-    for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
-      double[] powerAtFreq = new double[AudioCommon.REPETITIONS];
-      for (int j = 0; j < AudioCommon.REPETITIONS; j++) {
+  boolean isConsistent() {
+    double[] coeffOfVar = new double[Common.PIP_NUM];
+    for (int i = 0; i < Common.PIP_NUM; i++) {
+      double[] powerAtFreq = new double[Common.REPETITIONS];
+      for (int j = 0; j < Common.REPETITIONS; j++) {
         powerAtFreq[j] = power[i][j];
       }
       coeffOfVar[i] = Util.std(powerAtFreq) / Util.mean(powerAtFreq);
@@ -114,7 +94,7 @@
   /**
    * Determine test pass/fail using the frequency response. Package visible for unit testing.
    */
-  public boolean responsePassesHifiTest(double[] dB) {
+  boolean responsePassesHifiTest(double[] dB) {
     for (int i = 0; i < dB.length; i++) {
       // Precautionary; NaN should not happen.
       if (Double.isNaN(dB[i])) {
@@ -124,16 +104,16 @@
       }
     }
 
-    if (Util.mean(dB) - Util.mean(noiseDB) < AudioCommon.SIGNAL_MIN_STRENGTH_DB_ABOVE_NOISE) {
+    if (Util.mean(dB) - Util.mean(noiseDB) < Common.SIGNAL_MIN_STRENGTH_DB_ABOVE_NOISE) {
       listener.sendMessage("WARNING: Signal is too weak or background noise is too strong."
           + " Turn up the volume of the playback device or move to a quieter location.\n");
       return false;
     }
 
-    int indexOf2000Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 2000.0);
-    threshold = dB[indexOf2000Hz] + AudioCommon.PASSING_THRESHOLD_DB;
-    int indexOf18500Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 18500.0);
-    int indexOf20000Hz = Util.findClosest(AudioCommon.FREQUENCIES_ORIGINAL, 20000.0);
+    int indexOf2000Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 2000.0);
+    threshold = dB[indexOf2000Hz] + Common.PASSING_THRESHOLD_DB;
+    int indexOf18500Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 18500.0);
+    int indexOf20000Hz = Util.findClosest(Common.FREQUENCIES_ORIGINAL, 20000.0);
     double[] responseInRange = new double[indexOf20000Hz - indexOf18500Hz];
     System.arraycopy(dB, indexOf18500Hz, responseInRange, 0, responseInRange.length);
     if (Util.mean(responseInRange) < threshold) {
@@ -148,15 +128,15 @@
    * Calculate the Fourier Coefficient at the pip frequency to calculate the frequency response.
    * Package visible for unit testing.
    */
-  public double[] measurePipStrength() {
+  double[] measurePipStrength() {
     listener.sendMessage("Aligning data... Please wait...\n");
     final int dataStartI = alignData();
     final int prefixTotalLength = dataStartI
-        + Util.toLength(AudioCommon.PREFIX_LENGTH_S + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S, sampleRate);
+        + Util.toLength(Common.PREFIX_LENGTH_S + Common.PAUSE_AFTER_PREFIX_DURATION_S, sampleRate);
     listener.sendMessage("Done.\n");
     listener.sendMessage("Prefix starts at " + (double) dataStartI / sampleRate + " s \n");
-    if (dataStartI > Math.round(sampleRate * (AudioCommon.PREFIX_LENGTH_S
-            + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S))) {
+    if (dataStartI > Math.round(sampleRate * (Common.PREFIX_LENGTH_S
+            + Common.PAUSE_BEFORE_PREFIX_DURATION_S + Common.PAUSE_AFTER_PREFIX_DURATION_S))) {
       listener.sendMessage("WARNING: Unexpected prefix start time. May have missed the prefix.\n"
           + "PLAY button should be pressed on the playback device within one second"
           + " after RECORD is pressed on the recording device.\n"
@@ -165,17 +145,17 @@
     }
 
     listener.sendMessage("Analyzing noise strength... Please wait...\n");
-    noisePower = new double[AudioCommon.PIP_NUM][AudioCommon.NOISE_SAMPLES];
-    noiseDB = new double[AudioCommon.PIP_NUM];
-    for (int s = 0; s < AudioCommon.NOISE_SAMPLES; s++) {
-      double[] noisePoints = new double[AudioCommon.WINDOW_FOR_RECORDER.length];
+    noisePower = new double[Common.PIP_NUM][Common.NOISE_SAMPLES];
+    noiseDB = new double[Common.PIP_NUM];
+    for (int s = 0; s < Common.NOISE_SAMPLES; s++) {
+      double[] noisePoints = new double[Common.WINDOW_FOR_RECORDER.length];
       System.arraycopy(data, dataStartI - (s + 1) * noisePoints.length - 1,
           noisePoints, 0, noisePoints.length);
       for (int j = 0; j < noisePoints.length; j++) {
-        noisePoints[j] = noisePoints[j] * AudioCommon.WINDOW_FOR_RECORDER[j];
+        noisePoints[j] = noisePoints[j] * Common.WINDOW_FOR_RECORDER[j];
       }
-      for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
-        double freq = AudioCommon.FREQUENCIES_ORIGINAL[i];
+      for (int i = 0; i < Common.PIP_NUM; i++) {
+        double freq = Common.FREQUENCIES_ORIGINAL[i];
         Complex fourierCoeff = new Complex(0, 0);
         final Complex rotator = new Complex(0,
             -2.0 * Math.PI * freq / sampleRate).exp();
@@ -188,48 +168,48 @@
         noisePower[i][s] = fourierCoeff.multiply(fourierCoeff.conjugate()).abs();
       }
     }
-    for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+    for (int i = 0; i < Common.PIP_NUM; i++) {
       double meanNoisePower = 0;
-      for (int j = 0; j < AudioCommon.NOISE_SAMPLES; j++) {
+      for (int j = 0; j < Common.NOISE_SAMPLES; j++) {
         meanNoisePower += noisePower[i][j];
       }
-      meanNoisePower /= AudioCommon.NOISE_SAMPLES;
+      meanNoisePower /= Common.NOISE_SAMPLES;
       noiseDB[i] = 10 * Math.log10(meanNoisePower);
     }
 
     listener.sendMessage("Analyzing pips... Please wait...\n");
-    power = new double[AudioCommon.PIP_NUM][AudioCommon.REPETITIONS];
-    for (int i = 0; i < AudioCommon.PIP_NUM * AudioCommon.REPETITIONS; i++) {
-      if (i % AudioCommon.PIP_NUM == 0) {
-        listener.sendMessage("#" + (i / AudioCommon.PIP_NUM + 1) + "\n");
+    power = new double[Common.PIP_NUM][Common.REPETITIONS];
+    for (int i = 0; i < Common.PIP_NUM * Common.REPETITIONS; i++) {
+      if (i % Common.PIP_NUM == 0) {
+        listener.sendMessage("#" + (i / Common.PIP_NUM + 1) + "\n");
       }
 
       int pipExpectedStartI;
       pipExpectedStartI = prefixTotalLength
-          + Util.toLength(i * (AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S), sampleRate);
+          + Util.toLength(i * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S), sampleRate);
       // Cut out the data points for the current pip.
-      double[] pipPoints = new double[AudioCommon.WINDOW_FOR_RECORDER.length];
+      double[] pipPoints = new double[Common.WINDOW_FOR_RECORDER.length];
       System.arraycopy(data, pipExpectedStartI, pipPoints, 0, pipPoints.length);
-      for (int j = 0; j < AudioCommon.WINDOW_FOR_RECORDER.length; j++) {
-        pipPoints[j] = pipPoints[j] * AudioCommon.WINDOW_FOR_RECORDER[j];
+      for (int j = 0; j < Common.WINDOW_FOR_RECORDER.length; j++) {
+        pipPoints[j] = pipPoints[j] * Common.WINDOW_FOR_RECORDER[j];
       }
       Complex fourierCoeff = new Complex(0, 0);
       final Complex rotator = new Complex(0,
-          -2.0 * Math.PI * AudioCommon.FREQUENCIES[i] / sampleRate).exp();
+          -2.0 * Math.PI * Common.FREQUENCIES[i] / sampleRate).exp();
       Complex phasor = new Complex(1, 0);
       for (int j = 0; j < pipPoints.length; j++) {
         fourierCoeff = fourierCoeff.add(phasor.multiply(pipPoints[j]));
         phasor = phasor.multiply(rotator);
       }
       fourierCoeff = fourierCoeff.multiply(1.0 / pipPoints.length);
-      int j = AudioCommon.ORDER[i];
-      power[j % AudioCommon.PIP_NUM][j / AudioCommon.PIP_NUM] =
+      int j = Common.ORDER[i];
+      power[j % Common.PIP_NUM][j / Common.PIP_NUM] =
           fourierCoeff.multiply(fourierCoeff.conjugate()).abs();
     }
 
     // Calculate median of trials.
-    double[] dB = new double[AudioCommon.PIP_NUM];
-    for (int i = 0; i < AudioCommon.PIP_NUM; i++) {
+    double[] dB = new double[Common.PIP_NUM];
+    for (int i = 0; i < Common.PIP_NUM; i++) {
       dB[i] = 10 * Math.log10(Util.median(power[i]));
     }
     return dB;
@@ -238,52 +218,41 @@
   /**
    * Align data using prefix. Package visible for unit testing.
    */
-  public int alignData() {
+  int alignData() {
     // Zeropadding samples to add in the correlation to avoid FFT wraparound.
-    final int zeroPad =
-            Util.toLength(AudioCommon.PREFIX_LENGTH_S, AudioCommon.RECORDING_SAMPLE_RATE_HZ) - 1;
-    int fftSize = Util.nextPowerOfTwo((int) Math.round(sampleRate * (AudioCommon.PREFIX_LENGTH_S
-              + AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S
-              + AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S + 0.5))
+    final int zeroPad = Util.toLength(Common.PREFIX_LENGTH_S, Common.RECORDING_SAMPLE_RATE_HZ) - 1;
+    int fftSize = Util.nextPowerOfTwo((int) Math.round(sampleRate * (Common.PREFIX_LENGTH_S
+            + Common.PAUSE_BEFORE_PREFIX_DURATION_S + Common.PAUSE_AFTER_PREFIX_DURATION_S + 0.5))
         + zeroPad);
 
     double[] dataCut = new double[fftSize - zeroPad];
     System.arraycopy(data, 0, dataCut, 0, fftSize - zeroPad);
     double[] xCorrDataPrefix = Util.computeCrossCorrelation(
         Util.padZeros(Util.toComplex(dataCut), fftSize),
-        Util.padZeros(Util.toComplex(AudioCommon.PREFIX_FOR_RECORDER), fftSize));
+        Util.padZeros(Util.toComplex(Common.PREFIX_FOR_RECORDER), fftSize));
     return Util.findMaxIndex(xCorrDataPrefix);
   }
 
-  public double[] getDB() {
+  double[] getDB() {
     return dB;
   }
 
-  public double[][] getPower() {
+  double[][] getPower() {
     return power;
   }
 
-  public double[] getNoiseDB() {
+  double[] getNoiseDB() {
     return noiseDB;
   }
 
-  public double getThreshold() {
+  double getThreshold() {
     return threshold;
   }
 
-  public boolean getResult() {
+  boolean getResult() {
     return result;
   }
 
-  public boolean isSilence() {
-    for (int i = 0; i < data.length; i++) {
-      if (Math.abs(data[i]) > SILENCE_THRESHOLD) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   /**
    * An interface for listening a message publishing the progress of the analyzer.
    */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java
deleted file mode 100644
index 3ea5790..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/soundio/SoundGenerator.java
+++ /dev/null
@@ -1,105 +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 com.android.cts.verifier.audio.soundio;
-
-import com.android.cts.verifier.audio.audiolib.AudioCommon;
-import com.android.cts.verifier.audio.Util;
-
-/**
- * Sound generator.
- */
-public class SoundGenerator {
-
-  private static SoundGenerator instance;
-
-  private final byte[] generatedSound;
-  private final double[] sample;
-
-  private SoundGenerator() {
-    // Initialize sample.
-    int pipNum = AudioCommon.PIP_NUM;
-    int prefixTotalLength =
-          Util.toLength(AudioCommon.PREFIX_LENGTH_S, AudioCommon.PLAYING_SAMPLE_RATE_HZ)
-        + Util.toLength(AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S,
-                    AudioCommon.PLAYING_SAMPLE_RATE_HZ)
-        + Util.toLength(AudioCommon.PAUSE_AFTER_PREFIX_DURATION_S,
-                    AudioCommon.PLAYING_SAMPLE_RATE_HZ);
-    int repetitionLength = pipNum * Util.toLength(
-        AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S,
-            AudioCommon.PLAYING_SAMPLE_RATE_HZ);
-    int sampleLength = prefixTotalLength + AudioCommon.REPETITIONS * repetitionLength;
-    sample = new double[sampleLength];
-
-    // Fill sample with prefix.
-    System.arraycopy(AudioCommon.PREFIX_FOR_PLAYER, 0, sample,
-        Util.toLength(AudioCommon.PAUSE_BEFORE_PREFIX_DURATION_S,
-                AudioCommon.PLAYING_SAMPLE_RATE_HZ),
-        AudioCommon.PREFIX_FOR_PLAYER.length);
-
-    // Fill the sample.
-    for (int i = 0; i < pipNum * AudioCommon.REPETITIONS; i++) {
-      double[] pip = getPip(AudioCommon.WINDOW_FOR_PLAYER, AudioCommon.FREQUENCIES[i]);
-      System.arraycopy(pip, 0, sample,
-          prefixTotalLength + i * Util.toLength(
-              AudioCommon.PIP_DURATION_S + AudioCommon.PAUSE_DURATION_S,
-                  AudioCommon.PLAYING_SAMPLE_RATE_HZ),
-          pip.length);
-    }
-
-    // Convert sample to byte.
-    generatedSound = new byte[2 * sample.length];
-    int i = 0;
-    for (double dVal : sample) {
-      short val = (short) ((dVal * 32767));
-      generatedSound[i++] = (byte) (val & 0x00ff);
-      generatedSound[i++] = (byte) ((val & 0xff00) >>> 8);
-    }
-  }
-
-  public static SoundGenerator getInstance() {
-    if (instance == null) {
-      instance = new SoundGenerator();
-    }
-    return instance;
-  }
-
-  /**
-   * Gets a pip sample.
-   */
-  private static double[] getPip(double[] window, double frequency) {
-    int pipArrayLength = window.length;
-    double[] pipArray = new double[pipArrayLength];
-    double radPerSample = 2 * Math.PI / (AudioCommon.PLAYING_SAMPLE_RATE_HZ / frequency);
-    for (int i = 0; i < pipArrayLength; i++) {
-      pipArray[i] = window[i] * Math.sin(i * radPerSample);
-    }
-    return pipArray;
-  }
-
-  /**
-   * Get generated sound in byte[].
-   */
-  public byte[] getByte() {
-    return generatedSound;
-  }
-
-  /**
-   * Get sample in double[].
-   */
-  public double[] getSample() {
-    return sample;
-  }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java
deleted file mode 100644
index 71d3ec1..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/wavelib/RmsHelper.java
+++ /dev/null
@@ -1,98 +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 com.android.cts.verifier.audio.wavelib;
-
-public class RmsHelper {
-    private double mRmsCurrent;
-    public int mBlockSize;
-    private int mShoutCount;
-    public boolean mRunning = false;
-
-    private short[] mAudioShortArray;
-
-    private DspBufferDouble mRmsSnapshots;
-    private int mShotIndex;
-
-    private final float mMinRmsDb;
-    private final float mMinRmsVal;
-
-    private static final float MAX_VAL = (float)(1 << 15);
-
-    public RmsHelper(int blockSize, int shotCount, float minRmsDb, float minRmsVal) {
-        mBlockSize = blockSize;
-        mShoutCount = shotCount;
-        mMinRmsDb = minRmsDb;
-        mMinRmsVal = minRmsVal;
-
-        reset();
-    }
-
-    public void reset() {
-        mAudioShortArray = new short[mBlockSize];
-        mRmsSnapshots = new DspBufferDouble(mShoutCount);
-        mShotIndex = 0;
-        mRmsCurrent = 0;
-        mRunning = false;
-    }
-
-    public void captureShot() {
-        if (mShotIndex >= 0 && mShotIndex < mRmsSnapshots.getSize()) {
-            mRmsSnapshots.setValue(mShotIndex++, mRmsCurrent);
-        }
-    }
-
-    public void setRunning(boolean running) {
-        mRunning = running;
-    }
-
-    public double getRmsCurrent() {
-        return mRmsCurrent;
-    }
-
-    public DspBufferDouble getRmsSnapshots() {
-        return mRmsSnapshots;
-    }
-
-    public boolean updateRms(PipeShort pipe, int channelCount, int channel) {
-        if (mRunning) {
-            int samplesAvailable = pipe.availableToRead();
-            while (samplesAvailable >= mBlockSize) {
-                pipe.read(mAudioShortArray, 0, mBlockSize);
-
-                double rmsTempSum = 0;
-                int count = 0;
-                for (int i = channel; i < mBlockSize; i += channelCount) {
-                    float value = mAudioShortArray[i] / MAX_VAL;
-
-                    rmsTempSum += value * value;
-                    count++;
-                }
-                float rms = count > 0 ? (float)Math.sqrt(rmsTempSum / count) : 0f;
-                if (rms < mMinRmsVal) {
-                    rms = mMinRmsVal;
-                }
-
-                double alpha = 0.9;
-                double total_rms = rms * alpha + mRmsCurrent * (1.0f - alpha);
-                mRmsCurrent = total_rms;
-
-                samplesAvailable = pipe.availableToRead();
-            }
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
deleted file mode 100644
index 90fa098..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/CameraMuteToggleActivity.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 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.camera.its;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.ImageFormat;
-import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.Image;
-import android.media.ImageReader;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-import android.util.Size;
-import android.view.Surface;
-import android.view.TextureView;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-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.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.ex.camera2.blocking.BlockingCameraManager;
-import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-import com.android.ex.camera2.blocking.BlockingStateCallback;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Test for manual verification of camera privacy hardware switches
- * This test verifies that devices which implement camera hardware
- * privacy toggles enforce sensor privacy when toggles are enabled.
- * - The video stream should be muted:
- * - camera preview & capture should be blank
- * - A dialog or notification should be shown that informs
- * the user that the sensor privacy is enabled.
- */
-public class CameraMuteToggleActivity extends PassFailButtons.Activity
-        implements TextureView.SurfaceTextureListener,
-        ImageReader.OnImageAvailableListener {
-
-    private static final String TAG = "CameraMuteToggleActivity";
-    private static final int SESSION_READY_TIMEOUT_MS = 5000;
-    private static final int DEFAULT_CAMERA_IDX = 0;
-
-    private TextureView mPreviewView;
-    private SurfaceTexture mPreviewTexture;
-    private Surface mPreviewSurface;
-
-    private ImageView mImageView;
-
-    private CameraManager mCameraManager;
-    private HandlerThread mCameraThread;
-    private Handler mCameraHandler;
-    private BlockingCameraManager mBlockingCameraManager;
-    private CameraCharacteristics mCameraCharacteristics;
-    private BlockingStateCallback mCameraListener;
-
-    private BlockingSessionCallback mSessionListener;
-    private CaptureRequest.Builder mPreviewRequestBuilder;
-    private CaptureRequest mPreviewRequest;
-    private CaptureRequest.Builder mStillCaptureRequestBuilder;
-    private CaptureRequest mStillCaptureRequest;
-
-    private CameraCaptureSession mCaptureSession;
-    private CameraDevice mCameraDevice;
-
-    SizeComparator mSizeComparator = new SizeComparator();
-
-    private Size mPreviewSize;
-    private Size mJpegSize;
-    private ImageReader mJpegImageReader;
-
-    private CameraCaptureSession.CaptureCallback mCaptureCallback =
-            new CameraCaptureSession.CaptureCallback() {
-            };
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.cam_hw_toggle);
-
-        setPassFailButtonClickListeners();
-
-        mPreviewView = findViewById(R.id.preview_view);
-        mImageView = findViewById(R.id.image_view);
-
-        mPreviewView.setSurfaceTextureListener(this);
-
-        mCameraManager = getSystemService(CameraManager.class);
-
-        setInfoResources(R.string.camera_hw_toggle_test, R.string.camera_hw_toggle_test_info, -1);
-
-        // Enable Pass button only after taking photo
-        setPassButtonEnabled(false);
-        setTakePictureButtonEnabled(false);
-
-        mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
-        mCameraListener = new BlockingStateCallback();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        startBackgroundThread();
-
-        Exception cameraSetupException = null;
-        boolean enablePassButton = false;
-        try {
-            final String[] camerasList = mCameraManager.getCameraIdList();
-            if (camerasList.length > 0) {
-                String cameraId = mCameraManager.getCameraIdList()[DEFAULT_CAMERA_IDX];
-                setUpCamera(cameraId);
-            } else {
-                showCameraErrorText("");
-            }
-        } catch (CameraAccessException e) {
-            cameraSetupException = e;
-            // Enable Pass button for cameras that do not support mute patterns
-            // and will disconnect clients if sensor privacy is enabled
-            enablePassButton = (e.getReason() == CameraAccessException.CAMERA_DISABLED);
-        } catch (BlockingOpenException e) {
-            cameraSetupException = e;
-            enablePassButton = e.wasDisconnected();
-        } finally {
-            if (cameraSetupException != null) {
-                cameraSetupException.printStackTrace();
-                showCameraErrorText(cameraSetupException.getMessage());
-                setPassButtonEnabled(enablePassButton);
-            }
-        }
-    }
-
-    private void showCameraErrorText(String errorMsg) {
-        TextView instructionsText = findViewById(R.id.instruction_text);
-        instructionsText.setText(R.string.camera_hw_toggle_test_no_camera);
-        instructionsText.append(errorMsg);
-        setTakePictureButtonEnabled(false);
-    }
-
-    @Override
-    public void onPause() {
-        shutdownCamera();
-        stopBackgroundThread();
-
-        super.onPause();
-    }
-
-    @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
-            int width, int height) {
-        mPreviewTexture = surfaceTexture;
-
-        mPreviewSurface = new Surface(mPreviewTexture);
-
-        if (mCameraDevice != null) {
-            startPreview();
-        }
-    }
-
-    @Override
-    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-        // Ignored, Camera does all the work for us
-        Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height);
-    }
-
-    @Override
-    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-        mPreviewTexture = null;
-        return true;
-    }
-
-    @Override
-    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-        // Invoked every time there's a new Camera preview frame
-    }
-
-    @Override
-    public void onImageAvailable(ImageReader reader) {
-        Image img = null;
-        try {
-            img = reader.acquireNextImage();
-            if (img == null) {
-                Log.d(TAG, "Invalid image!");
-                return;
-            }
-            final int format = img.getFormat();
-
-            Bitmap imgBitmap = null;
-            if (format == ImageFormat.JPEG) {
-                ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
-                jpegBuffer.rewind();
-                byte[] jpegData = new byte[jpegBuffer.limit()];
-                jpegBuffer.get(jpegData);
-                imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
-                img.close();
-            } else {
-                Log.i(TAG, "Unsupported image format: " + format);
-            }
-            if (imgBitmap != null) {
-                final Bitmap bitmap = imgBitmap;
-                final boolean isMuted = isBitmapMuted(imgBitmap);
-                runOnUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        mImageView.setImageBitmap(bitmap);
-                        // enable pass button if image is muted (black)
-                        setPassButtonEnabled(isMuted);
-                    }
-                });
-            }
-        } catch (java.lang.IllegalStateException e) {
-            // Swallow exceptions
-            e.printStackTrace();
-        } finally {
-            if (img != null) {
-                img.close();
-            }
-        }
-    }
-
-    private boolean isBitmapMuted(final Bitmap imgBitmap) {
-        // black images may have pixels with values > 0
-        // because of JPEG compression artifacts
-        final float COLOR_THRESHOLD = 0.02f;
-        for (int y = 0; y < imgBitmap.getHeight(); y++) {
-            for (int x = 0; x < imgBitmap.getWidth(); x++) {
-                Color pixelColor = Color.valueOf(imgBitmap.getPixel(x, y));
-                if (pixelColor.red() > COLOR_THRESHOLD || pixelColor.green() > COLOR_THRESHOLD
-                        || pixelColor.blue() > COLOR_THRESHOLD) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    private class SizeComparator implements Comparator<Size> {
-        @Override
-        public int compare(Size lhs, Size rhs) {
-            long lha = lhs.getWidth() * lhs.getHeight();
-            long rha = rhs.getWidth() * rhs.getHeight();
-            if (lha == rha) {
-                lha = lhs.getWidth();
-                rha = rhs.getWidth();
-            }
-            return (lha < rha) ? -1 : (lha > rha ? 1 : 0);
-        }
-    }
-
-    private void setUpCamera(String cameraId) throws CameraAccessException, BlockingOpenException {
-        shutdownCamera();
-
-        mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
-        mCameraDevice = mBlockingCameraManager.openCamera(cameraId,
-                mCameraListener, mCameraHandler);
-
-        StreamConfigurationMap config =
-                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
-        Arrays.sort(jpegSizes, mSizeComparator);
-        // choose smallest image size, image capture is not the point of this test
-        mJpegSize = jpegSizes[0];
-
-        mJpegImageReader = ImageReader.newInstance(
-                mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1);
-        mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler);
-
-        if (mPreviewTexture != null) {
-            startPreview();
-        }
-    }
-
-    private void shutdownCamera() {
-        if (null != mCaptureSession) {
-            mCaptureSession.close();
-            mCaptureSession = null;
-        }
-        if (null != mCameraDevice) {
-            mCameraDevice.close();
-            mCameraDevice = null;
-        }
-        if (null != mJpegImageReader) {
-            mJpegImageReader.close();
-            mJpegImageReader = null;
-        }
-    }
-
-    /**
-     * Starts a background thread and its {@link Handler}.
-     */
-    private void startBackgroundThread() {
-        mCameraThread = new HandlerThread("CameraThreadBackground");
-        mCameraThread.start();
-        mCameraHandler = new Handler(mCameraThread.getLooper());
-    }
-
-    /**
-     * Stops the background thread and its {@link Handler}.
-     */
-    private void stopBackgroundThread() {
-        mCameraThread.quitSafely();
-        try {
-            mCameraThread.join();
-            mCameraThread = null;
-            mCameraHandler = null;
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private Size getPreviewSize(int minWidth) {
-        StreamConfigurationMap config =
-                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        Size[] outputSizes = config.getOutputSizes(SurfaceTexture.class);
-        Arrays.sort(outputSizes, mSizeComparator);
-        // choose smallest image size that's at least minWidth
-        // image capture is not the point of this test
-        for (Size outputSize : outputSizes) {
-            if (outputSize.getWidth() > minWidth) {
-                return outputSize;
-            }
-        }
-        return outputSizes[0];
-    }
-
-    private void startPreview() {
-        try {
-            mPreviewSize = getPreviewSize(256);
-
-            mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
-            mPreviewRequestBuilder =
-                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-            mPreviewRequestBuilder.addTarget(mPreviewSurface);
-
-            mStillCaptureRequestBuilder =
-                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
-            mStillCaptureRequestBuilder.addTarget(mPreviewSurface);
-            mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface());
-
-            mSessionListener = new BlockingSessionCallback();
-            List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3);
-            outputSurfaces.add(mPreviewSurface);
-            outputSurfaces.add(mJpegImageReader.getSurface());
-            mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler);
-            mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000);
-
-            mPreviewRequest = mPreviewRequestBuilder.build();
-            mStillCaptureRequest = mStillCaptureRequestBuilder.build();
-
-            mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler);
-
-            setTakePictureButtonEnabled(true);
-        } catch (CameraAccessException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void takePicture() {
-        try {
-            mCaptureSession.stopRepeating();
-            mSessionListener.getStateWaiter().waitForState(
-                    BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
-
-            mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler);
-        } catch (CameraAccessException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void setPassButtonEnabled(boolean enabled) {
-        ImageButton pass_button = findViewById(R.id.pass_button);
-        pass_button.setEnabled(enabled);
-    }
-
-    private void setTakePictureButtonEnabled(boolean enabled) {
-        Button takePhoto = findViewById(R.id.take_picture_button);
-        takePhoto.setOnClickListener(v -> takePicture());
-        takePhoto.setEnabled(enabled);
-    }
-
-    @Override
-    public void recordTestResults() {
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.submit();
-    }
-}
diff --git a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
index f107562..8682ef6 100644
--- a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
@@ -34,6 +34,16 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
+
+        <!-- This activity is used to show "Work Policy Info" in Settings for Managed Devices -->
+        <activity android:name=".WorkPolicyInfoActivity"
+                  android:exported="true"
+                  android:launchMode="singleTask">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
+            </intent-filter>
+        </activity>
     </application>
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
 </manifest>
\ No newline at end of file
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 b2d496c..a022936 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
@@ -31,36 +31,46 @@
     private static final int FIRST_API_LEVEL =
             SystemProperties.getInt("ro.product.first_api_level", 0);
 
+    private static void verifyVersion(int version) {
+        if (version == Build.VERSION_CODES.CUR_DEVELOPMENT) {
+            throw new RuntimeException("Invalid version: " + version);
+        }
+    }
+
     public static boolean isBefore(int version) {
+        verifyVersion(version);
         return Build.VERSION.SDK_INT < version;
     }
 
     public static boolean isBefore(String version) {
-        return Build.VERSION.SDK_INT < resolveVersionString(version);
+        return isBefore(resolveVersionString(version));
     }
 
     public static boolean isAfter(int version) {
+        verifyVersion(version);
         return Build.VERSION.SDK_INT > version;
     }
 
     public static boolean isAfter(String version) {
-        return Build.VERSION.SDK_INT > resolveVersionString(version);
+        return isAfter(resolveVersionString(version));
     }
 
     public static boolean isAtLeast(int version) {
+        verifyVersion(version);
         return Build.VERSION.SDK_INT >= version;
     }
 
     public static boolean isAtLeast(String version) {
-        return Build.VERSION.SDK_INT >= resolveVersionString(version);
+        return isAtLeast(resolveVersionString(version));
     }
 
     public static boolean isAtMost(int version) {
+        verifyVersion(version);
         return Build.VERSION.SDK_INT <= version;
     }
 
     public static boolean isAtMost(String version) {
-        return Build.VERSION.SDK_INT <= resolveVersionString(version);
+        return isAtMost(resolveVersionString(version));
     }
 
     public static int getApiLevel() {
@@ -68,19 +78,21 @@
     }
 
     public static boolean isFirstApiBefore(int version) {
+        verifyVersion(version);
         return FIRST_API_LEVEL < version;
     }
 
     public static boolean isFirstApiBefore(String version) {
-        return FIRST_API_LEVEL < resolveVersionString(version);
+        return isFirstApiBefore(resolveVersionString(version));
     }
 
     public static boolean isFirstApiAfter(int version) {
+        verifyVersion(version);
         return FIRST_API_LEVEL > version;
     }
 
     public static boolean isFirstApiAfter(String version) {
-        return FIRST_API_LEVEL > resolveVersionString(version);
+        return isFirstApiAfter(resolveVersionString(version));
     }
 
     public static boolean isFirstApiAtLeast(int version) {
@@ -88,7 +100,7 @@
     }
 
     public static boolean isFirstApiAtLeast(String version) {
-        return FIRST_API_LEVEL >= resolveVersionString(version);
+        return isFirstApiAtLeast(resolveVersionString(version));
     }
 
     public static boolean isFirstApiAtMost(int version) {
@@ -96,7 +108,7 @@
     }
 
     public static boolean isFirstApiAtMost(String version) {
-        return FIRST_API_LEVEL <= resolveVersionString(version);
+        return isFirstApiAtMost(resolveVersionString(version));
     }
 
     public static int getFirstApiLevel() {
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 aea1248..7a1dcbc 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -30,12 +30,14 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.INativeDevice;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.FileUtil;
 
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableCollection;
@@ -52,10 +54,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -80,6 +85,7 @@
     private static ImmutableList<SharedLibraryInfo> sSharedLibs;
     private static ImmutableMultimap<String, String> sSharedLibsPathsToName;
     private static ImmutableMultimap<String, String> sJarsToClasses;
+    private static ImmutableMultimap<String, String> sJarsToFiles;
 
     private DeviceSdkLevel mDeviceSdkLevel;
 
@@ -201,6 +207,7 @@
                     "Landroid/os/IVoldListener;",
                     "Landroid/os/IVoldMountCallback;",
                     "Landroid/os/IVoldTaskListener;",
+                    "Landroid/os/TouchOcclusionMode;",
                     "Landroid/os/storage/CrateMetadata;",
                     "Landroid/view/LayerMetadataKey;",
                     "Lcom/android/internal/annotations/CompositeRWLock;",
@@ -223,8 +230,16 @@
                     "Landroid/os/BlockUntrustedTouchesMode;",
                     "Landroid/os/IInputConstants;",
                     "Landroid/os/InputEventInjectionResult;",
-                    "Landroid/os/InputEventInjectionSync;"
-
+                    "Landroid/os/InputEventInjectionSync;",
+                    // TODO(b/242741880): Remove duplication between sdksandbox-service and
+                    // sdk-sandbox-framework
+                    "Landroid/app/sdksandbox/ILoadSdkCallback;",
+                    "Landroid/app/sdksandbox/IRequestSurfacePackageCallback;",
+                    "Landroid/app/sdksandbox/ISdkSandboxManager;",
+                    "Landroid/app/sdksandbox/ISdkSandboxProcessDeathCallback;",
+                    "Landroid/app/sdksandbox/ISendDataCallback;",
+                    "Landroid/app/sdksandbox/ISharedPreferencesSyncCallback;",
+                    "Landroid/app/sdksandbox/ISdkToServiceCallback;"
             );
 
     private static final String FEATURE_WEARABLE = "android.hardware.type.watch";
@@ -718,9 +733,14 @@
     private static final ImmutableSet<String> ADSERVICES_SANDBOX_APK_IN_APEX_BURNDOWN_LIST =
             ImmutableSet.of(
                 // /apex/com.android.adservices/javalib/service-sdksandbox.jar
+                "Landroid/app/sdksandbox/ISharedPreferencesSyncCallback;",
+                "Lcom/android/sdksandbox/IDataReceivedCallback;",
+                "Lcom/android/sdksandbox/ILoadSdkInSandboxCallback;",
+                "Lcom/android/sdksandbox/IRequestSurfacePackageFromSdkCallback;",
                 "Lcom/android/sdksandbox/ISdkSandboxManagerToSdkSandboxCallback;",
                 "Lcom/android/sdksandbox/ISdkSandboxService;",
-                "Lcom/android/sdksandbox/ISdkSandboxToSdkSandboxManagerCallback;"
+                "Lcom/android/sdksandbox/SandboxLatencyInfo-IA;",
+                "Lcom/android/sdksandbox/SandboxLatencyInfo;"
             );
 
     private static final ImmutableMap<String, ImmutableSet<String>> FULL_APK_IN_APEX_BURNDOWN =
@@ -798,6 +818,8 @@
         });
         sSharedLibsPathsToName = sharedLibsPathsToName.build();
 
+        final ImmutableSetMultimap.Builder<String, String> jarsToFiles =
+                ImmutableSetMultimap.builder();
         final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
                 ImmutableSetMultimap.builder();
         Stream.of(sBootclasspathJars.stream(),
@@ -805,21 +827,32 @@
                         sSharedLibJars.stream())
                 .reduce(Stream::concat).orElseGet(Stream::empty)
                 .parallel()
-                .forEach(jar -> {
+                .forEach(jarPath -> {
+                    File jar = null;
                     try {
+                        jar = pullJarFromDevice(testInfo.getDevice(), jarPath);
+
+                        ImmutableSet<String> files = getJarFileContents(jar);
+                        synchronized (jarsToFiles) {
+                            jarsToFiles.putAll(jarPath, files);
+                        }
+
                         ImmutableSet<String> classes =
-                                Classpaths.getClassDefsFromJar(testInfo.getDevice(), jar).stream()
+                                Classpaths.getClassDefsFromJar(jar).stream()
                                         .map(ClassDef::getType)
                                         // Inner classes always go with their parent.
                                         .filter(className -> !className.contains("$"))
                                         .collect(ImmutableSet.toImmutableSet());
                         synchronized (jarsToClasses) {
-                            jarsToClasses.putAll(jar, classes);
+                            jarsToClasses.putAll(jarPath, classes);
                         }
                     } catch (DeviceNotAvailableException | IOException e) {
                         throw new RuntimeException(e);
+                    } finally {
+                        FileUtil.deleteFile(jar);
                     }
                 });
+        sJarsToFiles = jarsToFiles.build();
         sJarsToClasses = jarsToClasses.build();
     }
 
@@ -983,9 +1016,11 @@
         Arrays.stream(collectApkInApexPaths())
                 .parallel()
                 .forEach(apk -> {
+                    File apkFile = null;
                     try {
+                        apkFile = pullJarFromDevice(getDevice(), apk);
                         final ImmutableSet<String> apkClasses =
-                                Classpaths.getClassDefsFromJar(getDevice(), apk).stream()
+                                Classpaths.getClassDefsFromJar(apkFile).stream()
                                         .map(ClassDef::getType)
                                         .collect(ImmutableSet.toImmutableSet());
                         // b/226559955: The directory paths containing APKs contain the build ID,
@@ -1009,6 +1044,8 @@
                         }
                     } catch (Exception e) {
                         throw new RuntimeException(e);
+                    } finally {
+                        FileUtil.deleteFile(apkFile);
                     }
                 });
         assertThat(perApkClasspathDuplicates).isEmpty();
@@ -1033,6 +1070,8 @@
                 .put("androidx.window.sidecar",
                     ImmutableSet.of("Landroidx/window/common/", "Landroidx/window/sidecar",
                         "Landroidx/window/util"))
+                .put("com.google.android.camera.experimental2019",
+                    ImmutableSet.of("Landroidx/annotation"))
                 .put("com.google.android.camera.experimental2020_midyear",
                     ImmutableSet.of("Landroidx/annotation"))
                 .build();
@@ -1047,6 +1086,71 @@
                 ).isEmpty();
     }
 
+    /**
+     * Ensure that there are no kotlin files in BOOTCLASSPATH, SYSTEMSERVERCLASSPATH
+     * and shared library jars.
+     */
+    @Test
+    public void testNoKotlinFilesInClasspaths() throws Exception {
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastT());
+        ImmutableList<String> kotlinFiles =
+                Stream.of(sBootclasspathJars.stream(),
+                        sSystemserverclasspathJars.stream(),
+                        sSharedLibJars.stream())
+                .reduce(Stream::concat).orElseGet(Stream::empty)
+                .parallel()
+                .filter(jarPath -> {
+                    return sJarsToFiles
+                            .get(jarPath)
+                            .stream()
+                            .anyMatch(file -> file.contains(".kotlin_builtins")
+                                    || file.contains(".kotlin_module"));
+                })
+                .collect(ImmutableList.toImmutableList());
+        assertThat(kotlinFiles).isEmpty();
+    }
+
+    /**
+     * Ensure that all classes from protobuf libraries are jarjared before
+     * included in BOOTCLASSPATH, SYSTEMSERVERCLASSPATH and shared library jars
+     */
+    @Test
+    public void testNoProtobufClassesWithoutJarjar() throws Exception {
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastU());
+        assertWithMessage("Classes from protobuf libraries must not be included in bootclasspath "
+            + "and systemserverclasspath without being jarjared.")
+                .that(Stream.of(sBootclasspathJars.stream(),
+                                sSystemserverclasspathJars.stream(),
+                                sSharedLibJars.stream())
+                        .reduce(Stream::concat).orElseGet(Stream::empty)
+                        .parallel()
+                        .filter(jarPath -> {
+                            return sJarsToClasses
+                                    .get(jarPath)
+                                    .stream()
+                                    .anyMatch(cls -> cls.startsWith("Lcom/google/protobuf/"));
+                        })
+                        .collect(ImmutableList.toImmutableList())
+                ).isEmpty();
+    }
+
+    private static File pullJarFromDevice(INativeDevice device,
+            String remoteJarPath) throws DeviceNotAvailableException {
+        File jar = device.pullFile(remoteJarPath);
+        if (jar == null) {
+            throw new IllegalStateException("could not pull remote file " + remoteJarPath);
+        }
+        return jar;
+    }
+
+    private static ImmutableSet<String> getJarFileContents(File jar) throws IOException {
+        try (JarFile jarFile = new JarFile(jar)) {
+            return jarFile.stream()
+                    .map(JarEntry::getName)
+                    .collect(ImmutableSet.toImmutableSet());
+        }
+    }
+
     private boolean isLegacyAndroidxDependency(
             ImmutableMap<String, ImmutableSet<String>> legacyExemptAndroidxSharedLibsNamesToClasses,
             String path, String className) {
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index d027e21..37c15ac 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -14,6 +14,17 @@
 # Bug component: 36137 = per-file SharedUserIdTest.java
 # Bug component: 36137 = per-file SplitTests.java
 
+# Storage bug component
+# Bug component: 46626 = per-file *Adoptable*
+# Bug component: 46626 = per-file *DirectBoot*
+# Bug component: 46626 = per-file *Storage*
+# Bug component: 46626 = per-file *Documents*
+# Bug component: 46626 = per-file ScopedDirectoryAccessTest.java
+
+# DocsUI bug component
+# Bug component: 46626 = per-file *Documents*
+# Bug component: 46626 = per-file ScopedDirectoryAccessTest.java
+
 patb@google.com
 per-file AccessSerialNumberTest.java = ewol@google.com
 per-file ApexSignatureVerificationTest.java = dariofreni@google.com
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 527e8ef..0e2a887 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -91,7 +91,7 @@
     // Tag as a CTS artifact
     test_suites: [
         "general-tests",
-        "mts",
+        "mts-mediaprovider",
         "cts",
     ],
 }
@@ -155,7 +155,7 @@
     // Tag as a CTS artifact
     test_suites: [
         "general-tests",
-        "mts",
+        "mts-mediaprovider",
         "cts",
     ],
 }
@@ -171,7 +171,7 @@
     // Tag as a CTS artifact
     test_suites: [
         "general-tests",
-        "mts",
+        "mts-mediaprovider",
         "cts",
     ],
 }
@@ -187,7 +187,7 @@
     // Tag as a CTS artifact
     test_suites: [
         "general-tests",
-        "mts",
+        "mts-mediaprovider",
         "cts",
     ],
 }
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index 717cf95..6fbf5f3 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -16,7 +16,7 @@
 package android.scopedstorage.cts;
 
 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
@@ -253,7 +253,7 @@
         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
             if (EXIF_METADATA_QUERY.equals(queryType)) {
-                intent.putExtra(queryType, getExifMetadata(new File(filePath)));
+                intent.putExtra(queryType, getExifMetadataFromFile(new File(filePath)));
             }
         } else {
             throw new IllegalStateException(
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 6a65c5b..f5c6af5 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -21,7 +21,7 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
@@ -79,6 +79,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.isAppInstalled;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.queryAudioFile;
 import static android.scopedstorage.cts.lib.TestUtils.queryFile;
 import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending;
 import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
@@ -134,6 +135,7 @@
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.RedactionTestHelper;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructStat;
@@ -875,7 +877,7 @@
                 // EXIF tags and might misleadingly think there are not tags to redact
                 out.getFD().sync();
 
-                HashMap<String, String> exif = getExifMetadata(jpgFile);
+                HashMap<String, String> exif = getExifMetadataFromFile(jpgFile);
                 assertExifMetadataMatch(exif, originalExif);
 
                 HashMap<String, String> exifFromTestApp =
@@ -1131,6 +1133,11 @@
     }
 
     @Test
+    // There is a minor bug which, alghough fixed in sc-dev (aosp/1834457),
+    // cannot be propagated to the already released sc-release branche
+    // (b/234145920), where mainline-modules are tested.
+    // Skip this test in S to avoid failures in outdated targets.
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testAppendUpdatesMtime() throws Exception {
         writeAndCheckMtime(true);
     }
@@ -1319,7 +1326,7 @@
                 // Sync file to disk to ensure file is fully written to the lower fs.
                 out.getFD().sync();
             }
-            HashMap<String, String> exif = getExifMetadata(imgFile);
+            HashMap<String, String> exif = getExifMetadataFromFile(imgFile);
             assertExifMetadataMatch(exif, originalExif);
 
             // Install test app
@@ -1514,6 +1521,10 @@
             // Assert we can read from the file
             assertFileContent(otherAppImageFile, BYTES_DATA1);
 
+            // Assert has access to redacted information
+            RedactionTestHelper.assertConsistentNonRedactedAccess(otherAppImageFile,
+                    R.raw.img_with_metadata);
+
             // Assert we can delete the file
             assertThat(otherAppImageFile.delete()).isTrue();
             assertThat(otherAppImageFile.exists()).isFalse();
@@ -1767,6 +1778,37 @@
     }
 
     /**
+     * Test that renaming file paths to an external directory such as Android/* and Android/* /*
+     * except Android/media/* /* is not allowed.
+     */
+    @Test
+    public void testRenameFileToAppSpecificDir() throws Exception {
+        final File testFile = new File(getExternalMediaDir(), IMAGE_FILE_NAME);
+        final File testFileNew = new File(getExternalMediaDir(), NONMEDIA_FILE_NAME);
+
+        try {
+            // Create a file in app's external media directory
+            if (!testFile.exists()) {
+                assertThat(testFile.createNewFile()).isTrue();
+            }
+
+            final String androidDirPath = getExternalStorageDir().getPath() + "/Android";
+
+            // Verify that we can't rename a file to Android/ or Android/data or
+            // Android/media directory
+            assertCantRenameFile(testFile, new File(androidDirPath, IMAGE_FILE_NAME));
+            assertCantRenameFile(testFile, new File(androidDirPath + "/data", IMAGE_FILE_NAME));
+            assertCantRenameFile(testFile, new File(androidDirPath + "/media", IMAGE_FILE_NAME));
+
+            // Verify that we can rename a file to app specific media directory.
+            assertCanRenameFile(testFile, testFileNew);
+        } finally {
+            testFile.delete();
+            testFileNew.delete();
+        }
+    }
+
+    /**
      * Test that renaming directories is allowed and aligns to default directory restrictions.
      */
     @Test
@@ -2987,6 +3029,45 @@
         }
     }
 
+    /**
+     * Test that renaming a file to {@link Environment#DIRECTORY_RINGTONES} sets
+     * {@link MediaStore.Audio.AudioColumns#IS_RINGTONE}
+     */
+
+    @Test
+    public void testRenameToRingtoneDirectory() throws Exception {
+        final File fileInDownloads = new File(getDownloadDir(), AUDIO_FILE_NAME);
+        final File fileInRingtones = new File(getRingtonesDir(), AUDIO_FILE_NAME);
+
+        try {
+            assertThat(fileInDownloads.createNewFile()).isTrue();
+            assertThat(MediaStore.scanFile(getContentResolver(), fileInDownloads)).isNotNull();
+
+            assertCanRenameFile(fileInDownloads, fileInRingtones);
+
+            try (Cursor c = queryAudioFile(fileInRingtones,
+                    MediaStore.Audio.AudioColumns.IS_RINGTONE)) {
+                assertTrue(c.moveToFirst());
+                assertWithMessage("Expected " + MediaStore.Audio.AudioColumns.IS_RINGTONE
+                        + " to be set after renaming to " + fileInRingtones)
+                        .that(c.getInt(0)).isEqualTo(1);
+            }
+
+            assertCanRenameFile(fileInRingtones, fileInDownloads);
+
+            try (Cursor c = queryAudioFile(fileInDownloads,
+                    MediaStore.Audio.AudioColumns.IS_RINGTONE)) {
+                assertTrue(c.moveToFirst());
+                assertWithMessage("Expected " + MediaStore.Audio.AudioColumns.IS_RINGTONE
+                        + " to be unset after renaming to " + fileInDownloads)
+                        .that(c.getInt(0)).isEqualTo(0);
+            }
+        } finally {
+            fileInDownloads.delete();
+            fileInRingtones.delete();
+        }
+    }
+
     @Test
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     public void testTransformsDirFileOperations() throws Exception {
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java
index 41a47b9..4bff21b 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/StableUrisTest.java
@@ -33,6 +33,8 @@
 import android.app.Instrumentation;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
 import android.provider.MediaStore;
 import android.scopedstorage.cts.lib.TestUtils;
 import android.util.Log;
@@ -121,7 +123,7 @@
             Log.d(TAG, "maxRowIdOfExternalDbBeforeReset:" + maxRowIdOfExternalDbBeforeReset);
 
             // Clear MediaProvider package data to trigger DB recreation.
-            mDevice.executeShellCommand("pm clear com.google.android.providers.media.module");
+            mDevice.executeShellCommand("pm clear " + getMediaProviderPackageName());
             waitForMountedAndIdleState(mContentResolver);
             MediaStore.scanVolume(mContentResolver, mVolumeName);
 
@@ -172,4 +174,11 @@
         return files;
     }
 
+    private static String getMediaProviderPackageName() {
+        final Instrumentation inst = androidx.test.InstrumentationRegistry.getInstrumentation();
+        final PackageManager packageManager = inst.getContext().getPackageManager();
+        final ProviderInfo providerInfo = packageManager.resolveContentProvider(
+                MediaStore.AUTHORITY, PackageManager.MATCH_ALL);
+        return providerInfo.packageName;
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index cd9378d..c7887f8 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -95,6 +95,16 @@
     }
 
     @Test
+    public void testManageExternalStorageCanReadRedactedContents() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanReadRedactedContents");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/RedactionTestHelper.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/RedactionTestHelper.java
index b7509e6..ee0d203 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/RedactionTestHelper.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/RedactionTestHelper.java
@@ -21,11 +21,15 @@
 import static org.junit.Assert.fail;
 
 import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.provider.MediaStore;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RawRes;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -56,12 +60,26 @@
      * Retrieve the EXIF metadata from the given file.
      */
     @NonNull
-    public static HashMap<String, String> getExifMetadata(@NonNull File file) throws IOException {
+    public static HashMap<String, String> getExifMetadataFromFile(@NonNull File file)
+            throws IOException {
         final ExifInterface exif = new ExifInterface(file);
         return dumpExifGpsTagsToMap(exif);
     }
 
     /**
+     * Retrieve the EXIF metadata from the given uri.
+     */
+    @NonNull
+    private static HashMap<String, String> getExifMetadataFromUri(@NonNull Uri uri)
+            throws IOException {
+        try (InputStream is = getContext().getContentResolver().openInputStream(uri)) {
+            final ExifInterface exif = new ExifInterface(is);
+            return dumpExifGpsTagsToMap(exif);
+        }
+    }
+
+
+    /**
      * Retrieve the EXIF metadata from the given resource.
      */
     @NonNull
@@ -116,4 +134,26 @@
         }
         return res;
     }
+
+    public static void assertConsistentNonRedactedAccess(File file, int metadataResId)
+            throws Exception {
+        // Write some meta-data to the file to assert on redacted information access
+        try (InputStream in =
+                     getContext().getResources().openRawResource(metadataResId);
+             FileOutputStream out = new FileOutputStream(file)) {
+            FileUtils.copy(in, out);
+            out.getFD().sync();
+        }
+
+        HashMap<String, String> originalExif = getExifMetadataFromRawResource(metadataResId);
+
+        // Using File API
+        HashMap<String, String> exifFromFilePath = getExifMetadataFromFile(file);
+        assertExifMetadataMatch(exifFromFilePath, originalExif);
+
+        Uri uri = MediaStore.scanFile(getContext().getContentResolver(), file);
+        // Using ContentResolver API
+        HashMap<String, String> exifFromContentResolver = getExifMetadataFromUri(uri);
+        assertExifMetadataMatch(exifFromContentResolver, originalExif);
+    }
 }
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 0670639..2e694ed 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -751,6 +751,17 @@
     }
 
     /**
+     * Queries {@link ContentResolver} for an audio file and returns a {@link Cursor} with the given
+     * columns.
+     */
+    @NonNull
+    public static Cursor queryAudioFile(File file, String... projection) {
+        return queryFile(getContentResolver(),
+                MediaStore.Audio.Media.getContentUri(sStorageVolumeName), file,
+                /*includePending*/ true, projection);
+    }
+
+    /**
      * Queries {@link ContentResolver} for a file and returns the corresponding mime type for its
      * entry in the database.
      */
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 6bcaa5a..1bb701b 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -92,6 +92,7 @@
 import android.os.storage.StorageManager;
 import android.platform.test.annotations.AppModeInstant;
 import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.RedactionTestHelper;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -224,6 +225,25 @@
     }
 
     @Test
+    public void testManageExternalStorageCanReadRedactedContents() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File otherAppImage = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+
+        try {
+            // Create file as another app
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
+
+            // Assert has access to redacted information
+            RedactionTestHelper.assertConsistentNonRedactedAccess(otherAppImage,
+                    R.raw.img_with_metadata);
+
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+        }
+    }
+
+    @Test
     public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
         pollForManageExternalStorageAllowed();
 
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
index be665ba..7f29d44 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -69,10 +69,6 @@
                 .addAllowedLogSource("AID_SYSTEM")
                 .addAllowedLogSource("AID_BLUETOOTH")
                 .addAllowedLogSource("com.android.bluetooth")
-                // TODO(b/236681553): Remove this.
-                .addAllowedLogSource("com.android.bluetooth.services")
-                // TODO(b/236681553): Remove this.
-                .addAllowedLogSource("com.google.android.bluetooth.services")
                 .addAllowedLogSource("AID_LMKD")
                 .addAllowedLogSource("AID_MEDIA")
                 .addAllowedLogSource("AID_RADIO")
diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING
index f48e90c..2a55a28 100644
--- a/tests/PhotoPicker/TEST_MAPPING
+++ b/tests/PhotoPicker/TEST_MAPPING
@@ -1,6 +1,26 @@
 {
+  "mainline-presubmit": [
+    {
+      "name": "CtsPhotoPickerTest[com.google.android.mediaprovider.apex]",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ],
   "presubmit": [
     {
+      "name": "CtsPhotoPickerTest",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
       "name": "CtsPhotoPickerTest"
     }
   ]
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/ActionGetContentOnlyTest.java b/tests/PhotoPicker/src/android/photopicker/cts/ActionGetContentOnlyTest.java
new file mode 100644
index 0000000..c7824bb
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/ActionGetContentOnlyTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.photopicker.cts;
+
+import static android.photopicker.cts.util.GetContentActivityAliasUtils.clearPackageData;
+import static android.photopicker.cts.util.GetContentActivityAliasUtils.getDocumentsUiPackageName;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUriAndPath;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAndClickBrowse;
+
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.photopicker.cts.util.GetContentActivityAliasUtils;
+import android.photopicker.cts.util.PhotoPickerUiUtils;
+import android.util.Pair;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker tests for PhotoPicker launched via {@link Intent#ACTION_GET_CONTENT} intent
+ * exclusively.
+ */
+public class ActionGetContentOnlyTest extends PhotoPickerBaseTest {
+
+    public static final String TAG = "ActionGetContentOnlyTest";
+
+    private static String sDocumentsUiPackageName;
+    private static int sGetContentTakeOverActivityAliasState;
+
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext);
+        }
+        mUriList.clear();
+
+        if (mActivity != null) {
+            mActivity.finish();
+        }
+    }
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        sDocumentsUiPackageName = getDocumentsUiPackageName();
+        sGetContentTakeOverActivityAliasState = GetContentActivityAliasUtils.enableAndGetOldState();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        clearPackageData(sDocumentsUiPackageName);
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+        GetContentActivityAliasUtils.restoreState(sGetContentTakeOverActivityAliasState);
+    }
+
+    @Test
+    public void testMimeTypeFilter() throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("audio/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+        // Should open documentsUi
+        assertThatShowsDocumentsUiButtons();
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test that DocumentsUi is opened.
+    }
+
+    @Test
+    public void testExtraMimeTypeFilter() throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("image/*");
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"video/*", "audio/*"});
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+        // Should open documentsUi
+        assertThatShowsDocumentsUiButtons();
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test that DocumentsUi is opened.
+    }
+
+    @Test
+    public void testBrowse_singleSelect() throws Exception {
+        final int itemCount = 1;
+        List<Pair<Uri, String>> createdImagesData = createImagesAndGetUriAndPath(itemCount,
+                mContext.getUserId(), /* isFavorite */ false);
+
+        final List<String> fileNameList = new ArrayList<>();
+        for (Pair<Uri, String> createdImageData: createdImagesData) {
+            mUriList.add(createdImageData.first);
+            fileNameList.add(createdImageData.second);
+        }
+
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("image/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        findAndClickBrowse(mDevice);
+
+        findAndClickFilesInDocumentsUi(fileNameList);
+
+        final Uri uri = mActivity.getResult().data.getData();
+
+        assertReadOnlyAccess(uri, mContext.getContentResolver());
+    }
+
+    @Test
+    public void testBrowse_multiSelect() throws Exception {
+        final int itemCount = 3;
+        List<Pair<Uri, String>> createdImagesData = createImagesAndGetUriAndPath(itemCount,
+                mContext.getUserId(), /* isFavorite */ false);
+
+        final List<String> fileNameList = new ArrayList<>();
+        for (Pair<Uri, String> createdImageData: createdImagesData) {
+            mUriList.add(createdImageData.first);
+            fileNameList.add(createdImageData.second);
+        }
+
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+        intent.setType("image/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        findAndClickBrowse(mDevice);
+
+        findAndClickFilesInDocumentsUi(fileNameList);
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount);
+        for (int i = 0; i < count; i++) {
+            assertReadOnlyAccess(clipData.getItemAt(i).getUri(), mContext.getContentResolver());
+        }
+    }
+
+    @Test
+    public void testChooserIntent_mediaFilter() throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("image/*");
+        mActivity.startActivityForResult(Intent.createChooser(intent, TAG), REQUEST_CODE);
+
+        // Should open Picker
+        assertThatShowsPickerUi();
+    }
+
+    @Test
+    public void testChooserIntent_nonMediaFilter() throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("*/*");
+        mActivity.startActivityForResult(Intent.createChooser(intent, TAG), REQUEST_CODE);
+
+        // Should open DocumentsUi
+        assertThatShowsDocumentsUiButtons();
+    }
+
+    @Test
+    public void testPickerSupportedFromDocumentsUi() throws Exception {
+        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+        intent.setType("*/*");
+        mActivity.startActivityForResult(Intent.createChooser(intent, TAG), REQUEST_CODE);
+
+        findAndClickMediaIcon();
+
+        // Should open Picker
+        assertThatShowsPickerUi();
+    }
+
+    private void findAndClickMediaIcon() throws Exception {
+        final UiSelector appList = new UiSelector().resourceId(sDocumentsUiPackageName
+                + ":id/apps_row");
+
+        // Wait for the first app list item to appear
+        assertWithMessage("Waiting for app list to appear in DocumentsUi").that(
+                new UiObject(appList).waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        String photoPickerAppName = "Media";
+        UiObject mediaButton = mDevice.findObject(new UiSelector().text(photoPickerAppName));
+
+        assertWithMessage("Timed out waiting for " + photoPickerAppName + " app icon to appear")
+                .that(new UiScrollable(appList).scrollIntoView(mediaButton)).isTrue();
+        mDevice.waitForIdle();
+
+        clickAndWait(mDevice, mediaButton);
+    }
+
+    private void assertThatShowsPickerUi() {
+        // Assert that Search bar for DocumentsUi shows
+        // Add a short timeout wait for DocumentsUi to show
+        assertThat(new UiObject(new UiSelector().resourceIdMatches(
+                PhotoPickerUiUtils.REGEX_PACKAGE_NAME + ":id/bottom_sheet"))
+                .waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        // Assert that "Recent files" header for DocumentsUi shows
+        assertThat(new UiObject(new UiSelector().resourceIdMatches(
+                PhotoPickerUiUtils.REGEX_PACKAGE_NAME + ":id/privacy_text"))
+                .exists()).isTrue();
+
+        // Assert that Documents list UiObject for DocumentsUi shows
+        assertThat(new UiObject(new UiSelector().text("Photos")).exists()).isTrue();
+        assertThat(new UiObject(new UiSelector().text("Albums")).exists()).isTrue();
+    }
+
+    private void assertThatShowsDocumentsUiButtons() {
+        // Assert that "Recent files" header for DocumentsUi shows
+        // Add a short timeout wait for DocumentsUi to show
+        assertThat(new UiObject(new UiSelector().resourceId(sDocumentsUiPackageName
+                + ":id/header_title")).waitForExists(SHORT_TIMEOUT)).isTrue();
+    }
+
+    private UiObject findSaveButton() {
+        return new UiObject(new UiSelector().resourceId(
+                        sDocumentsUiPackageName + ":id/container_save")
+                .childSelector(new UiSelector().resourceId("android:id/button1")));
+    }
+
+    private void findAndClickFilesInDocumentsUi(List<String> fileNameList) throws Exception {
+        for (String fileName : fileNameList) {
+            findAndClickFileInDocumentsUi(fileName);
+        }
+        findAndClickSelect();
+    }
+
+    private void findAndClickSelect() throws Exception {
+        final UiObject selectButton = new UiObject(new UiSelector().resourceId(
+                sDocumentsUiPackageName + ":id/action_menu_select"));
+        clickAndWait(mDevice, selectButton);
+    }
+
+    private void findAndClickFileInDocumentsUi(String fileName) throws Exception {
+        final UiSelector docList = new UiSelector().resourceId(sDocumentsUiPackageName
+                + ":id/dir_list");
+
+        // Wait for the first list item to appear
+        assertWithMessage("First list item").that(
+                new UiObject(docList.childSelector(new UiSelector()))
+                        .waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        try {
+            // Enforce to set the list mode
+            // Because UiScrollable can't reach the real bottom (when WEB_LINKABLE_FILE item)
+            // in grid mode when screen landscape mode
+            clickAndWait(mDevice, new UiObject(new UiSelector().resourceId(sDocumentsUiPackageName
+                    + ":id/sub_menu_list")));
+        } catch (UiObjectNotFoundException ignored) {
+            // Do nothing, already be in list mode.
+        }
+
+        // Repeat swipe gesture to find our item
+        // (UiScrollable#scrollIntoView does not seem to work well with SwipeRefreshLayout)
+        UiObject targetObject = new UiObject(docList.childSelector(new UiSelector()
+                .textContains(fileName)));
+        UiObject saveButton = findSaveButton();
+        int stepLimit = 10;
+        while (stepLimit-- > 0) {
+            if (targetObject.exists()) {
+                boolean targetObjectFullyVisible = !saveButton.exists()
+                        || targetObject.getVisibleBounds().bottom
+                        <= saveButton.getVisibleBounds().top;
+                if (targetObjectFullyVisible) {
+                    break;
+                }
+            }
+
+            mDevice.swipe(/* startX= */ mDevice.getDisplayWidth() / 2,
+                    /* startY= */ mDevice.getDisplayHeight() / 2,
+                    /* endX= */ mDevice.getDisplayWidth() / 2,
+                    /* endY= */ 0,
+                    /* steps= */ 40);
+        }
+
+        targetObject.longClick();
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java b/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java
new file mode 100644
index 0000000..0f61dc7
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/ActionPickImagesOnlyTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPersistedGrant;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker tests for {@link MediaStore#ACTION_PICK_IMAGES} intent exclusively
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActionPickImagesOnlyTest extends PhotoPickerBaseTest {
+
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext);
+        }
+        mUriList.clear();
+
+        if (mActivity != null) {
+            mActivity.finish();
+        }
+    }
+
+    @Test
+    public void testMultiSelect_invalidParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_invalidNegativeParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+        final int maxCount = 2;
+        final int imageCount = maxCount + 1;
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select maxCount + 1 item
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(mDevice, itemList.get(i));
+        }
+
+        UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+                "Select up to 2 items"));
+        assertWithMessage("Timed out while waiting for snackbar to appear").that(
+                snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        assertWithMessage("Timed out waiting for snackbar to disappear").that(
+                snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+        clickAndWait(mDevice, findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(maxCount);
+    }
+
+    @Test
+    public void testDoesNotRespectExtraAllowMultiple() throws Exception {
+        final int imageCount = 2;
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select 1 item
+        clickAndWait(mDevice, itemList.get(0));
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertPersistedGrant(uri, mContext.getContentResolver());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testMimeTypeFilter() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.setType("audio/*");
+        assertThrows(ActivityNotFoundException.class,
+                () -> mActivity.startActivityForResult(intent, REQUEST_CODE));
+    }
+
+    @Test
+    public void testExtraMimeTypeFilter() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"audio/*"});
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java
index aa0c954..ceaf3b2 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/CloudPhotoPickerTest.java
@@ -21,7 +21,7 @@
 import static android.photopicker.cts.PickerProviderMediaGenerator.setCloudProvider;
 import static android.photopicker.cts.PickerProviderMediaGenerator.syncCloudProvider;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
-import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris;
 import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
@@ -120,7 +120,7 @@
 
     @Test
     public void testCloudPlusLocalSyncWithoutDedupe() throws Exception {
-        createImages(1, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(1, mContext.getUserId()));
         initPrimaryCloudProviderWithImage(Pair.create(null, CLOUD_ID1));
 
         final ClipData clipData = fetchPickerMedia(2);
@@ -131,7 +131,7 @@
 
     @Test
     public void testCloudPlusLocalSyncWithDedupe() throws Exception {
-        createImages(1, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(1, mContext.getUserId()));
         initPrimaryCloudProviderWithImage(Pair.create(mUriList.get(0).getLastPathSegment(),
                         CLOUD_ID1));
 
@@ -295,7 +295,7 @@
         // Create a placeholder local image to ensure that the picker UI is never empty.
         // The PhotoPickerUiUtils#findItemList needs to select an item and it times out if the
         // Picker UI is empty.
-        createImages(1, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(1, mContext.getUserId()));
 
         // Cloud provider isn't set
         assertThat(MediaStore.isCurrentCloudMediaProviderAuthority(mContext.getContentResolver(),
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
index 1092406..48c1ea1 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -18,7 +18,7 @@
 
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
-import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris;
 import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
@@ -33,6 +33,7 @@
 import android.net.Uri;
 import android.provider.MediaStore;
 
+import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.uiautomator.UiObject;
 import androidx.test.uiautomator.UiSelector;
@@ -55,6 +56,7 @@
  * Photo Picker Device only tests for cross profile interaction flows.
  */
 @RunWith(BedsteadJUnit4.class)
+@LargeTest
 public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest {
     @ClassRule @Rule
     public static final DeviceState sDeviceState = new DeviceState();
@@ -79,7 +81,7 @@
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testWorkApp_canAccessPersonalProfileContents() throws Exception {
         final int imageCount = 2;
-        createImages(imageCount, sDeviceState.primaryUser().id(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(imageCount, sDeviceState.primaryUser().id()));
 
         Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
         intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
index cc8022d3..440e472 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -16,16 +16,20 @@
 
 package android.photopicker.cts;
 
+import static android.photopicker.cts.util.GetContentActivityAliasUtils.clearPackageData;
+import static android.photopicker.cts.util.GetContentActivityAliasUtils.getDocumentsUiPackageName;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertContainsMimeType;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPersistedGrant;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
 import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
-import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos;
-import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
-import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideosAndGetUris;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImagesAndGetUris;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideosAndGetUris;
 import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.clickAndWait;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
 import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
@@ -34,32 +38,62 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.app.Activity;
 import android.content.ClipData;
 import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
 import android.net.Uri;
+import android.photopicker.cts.util.GetContentActivityAliasUtils;
 import android.provider.MediaStore;
 
-import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiObject;
 import androidx.test.uiautomator.UiObjectNotFoundException;
 import androidx.test.uiautomator.UiSelector;
 
 import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * Photo Picker Device only tests for common flows.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class PhotoPickerTest extends PhotoPickerBaseTest {
+
+    @Parameter(0)
+    public String mAction;
+
+    @Parameters(name = "intent={0}")
+    public static Iterable<? extends Object> data() {
+        return getTestParameters();
+    }
+
     private List<Uri> mUriList = new ArrayList<>();
 
+    private static int sGetContentTakeOverActivityAliasState;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        sGetContentTakeOverActivityAliasState = GetContentActivityAliasUtils.enableAndGetOldState();
+        clearPackageData(getDocumentsUiPackageName());
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+        GetContentActivityAliasUtils.restoreState(sGetContentTakeOverActivityAliasState);
+    }
+
     @After
     public void tearDown() throws Exception {
         for (Uri uri : mUriList) {
@@ -75,13 +109,13 @@
     @Test
     public void testSingleSelect() throws Exception {
         final int itemCount = 1;
-        createImages(itemCount, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId()));
 
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final Intent intent = new Intent(mAction);
+        launchPhotoPickerForIntent(intent);
 
         final UiObject item = findItemList(itemCount).get(0);
-        clickAndWait(item);
+        clickAndWait(mDevice, item);
 
         final Uri uri = mActivity.getResult().data.getData();
         assertPickerUriFormat(uri, mContext.getUserId());
@@ -92,19 +126,20 @@
     @Test
     public void testSingleSelectForFavoritesAlbum() throws Exception {
         final int itemCount = 1;
-        createImages(itemCount, mContext.getUserId(), mUriList, true);
+        mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId(),
+                /* isFavorite */ true));
 
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final Intent intent = new Intent(mAction);
+        launchPhotoPickerForIntent(intent);
 
         UiObject albumsTab = mDevice.findObject(new UiSelector().text(
                 "Albums"));
-        clickAndWait(albumsTab);
+        clickAndWait(mDevice, albumsTab);
         final UiObject album = findItemList(1).get(0);
-        clickAndWait(album);
+        clickAndWait(mDevice, album);
 
         final UiObject item = findItemList(itemCount).get(0);
-        clickAndWait(item);
+        clickAndWait(mDevice, item);
 
         final Uri uri = mActivity.getResult().data.getData();
         assertPickerUriFormat(uri, mContext.getUserId());
@@ -114,18 +149,18 @@
     @Test
     public void testLaunchPreviewMultipleForVideoAlbum() throws Exception {
         final int videoCount = 2;
-        createVideos(videoCount, mContext.getUserId(), mUriList);
+        mUriList.addAll(createVideosAndGetUris(videoCount, mContext.getUserId()));
 
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        Intent intent = new Intent(mAction);
         intent.setType("video/*");
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        addMultipleSelectionFlag(intent);
+        launchPhotoPickerForIntent(intent);
 
         UiObject albumsTab = mDevice.findObject(new UiSelector().text(
                 "Albums"));
-        clickAndWait(albumsTab);
+        clickAndWait(mDevice, albumsTab);
         final UiObject album = findItemList(1).get(0);
-        clickAndWait(album);
+        clickAndWait(mDevice, album);
 
         final List<UiObject> itemList = findItemList(videoCount);
         final int itemCount = itemList.size();
@@ -133,10 +168,10 @@
         assertThat(itemCount).isEqualTo(videoCount);
 
         for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
+            clickAndWait(mDevice, itemList.get(i));
         }
 
-        clickAndWait(findViewSelectedButton());
+        clickAndWait(mDevice, findViewSelectedButton());
 
         // Wait for playback to start. This is needed in some devices where playback
         // buffering -> ready state takes around 10s.
@@ -147,10 +182,10 @@
     @Test
     public void testSingleSelectWithPreview() throws Exception {
         final int itemCount = 1;
-        createImages(itemCount, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(itemCount, mContext.getUserId()));
 
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final Intent intent = new Intent(mAction);
+        launchPhotoPickerForIntent(intent);
 
         final UiObject item = findItemList(itemCount).get(0);
         item.longClick();
@@ -158,7 +193,7 @@
 
         final UiObject addButton = findPreviewAddOrSelectButton();
         assertThat(addButton.waitForExists(1000)).isTrue();
-        clickAndWait(addButton);
+        clickAndWait(mDevice, addButton);
 
         final Uri uri = mActivity.getResult().data.getData();
         assertPickerUriFormat(uri, mContext.getUserId());
@@ -166,91 +201,21 @@
     }
 
     @Test
-    public void testMultiSelect_invalidParam() throws Exception {
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-        final GetResultActivity.Result res = mActivity.getResult();
-        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void testMultiSelect_invalidNegativeParam() throws Exception {
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-        final GetResultActivity.Result res = mActivity.getResult();
-        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
-        final int maxCount = 2;
-        final int imageCount = maxCount + 1;
-        createImages(imageCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-
-        final List<UiObject> itemList = findItemList(imageCount);
-        final int itemCount = itemList.size();
-        assertThat(itemCount).isEqualTo(imageCount);
-        // Select maxCount + 1 item
-        for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
-        }
-
-        UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
-                "Select up to 2 items"));
-        assertWithMessage("Timed out while waiting for snackbar to appear").that(
-                snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
-
-        assertWithMessage("Timed out waiting for snackbar to disappear").that(
-                snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
-
-        clickAndWait(findAddButton());
-
-        final ClipData clipData = mActivity.getResult().data.getClipData();
-        final int count = clipData.getItemCount();
-        assertThat(count).isEqualTo(maxCount);
-    }
-
-    @Test
-    public void testDoesNotRespectExtraAllowMultiple() throws Exception {
-        final int imageCount = 2;
-        createImages(imageCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
-
-        final List<UiObject> itemList = findItemList(imageCount);
-        final int itemCount = itemList.size();
-        assertThat(itemCount).isEqualTo(imageCount);
-        // Select 1 item
-        clickAndWait(itemList.get(0));
-
-        final Uri uri = mActivity.getResult().data.getData();
-        assertPickerUriFormat(uri, mContext.getUserId());
-        assertPersistedGrant(uri, mContext.getContentResolver());
-        assertRedactedReadOnlyAccess(uri);
-    }
-
-    @Test
     public void testMultiSelect() throws Exception {
         final int imageCount = 4;
-        createImages(imageCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+        Intent intent = new Intent(mAction);
+        addMultipleSelectionFlag(intent);
+        launchPhotoPickerForIntent(intent);
 
         final List<UiObject> itemList = findItemList(imageCount);
         final int itemCount = itemList.size();
         assertThat(itemCount).isEqualTo(imageCount);
         for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
+            clickAndWait(mDevice, itemList.get(i));
         }
 
-        clickAndWait(findAddButton());
+        clickAndWait(mDevice, findAddButton());
 
         final ClipData clipData = mActivity.getResult().data.getClipData();
         final int count = clipData.getItemCount();
@@ -266,18 +231,19 @@
     @Test
     public void testMultiSelect_longPress() throws Exception {
         final int videoCount = 3;
-        createDNGVideos(videoCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mUriList.addAll(createDNGVideosAndGetUris(videoCount, mContext.getUserId()));
+
+        Intent intent = new Intent(mAction);
         intent.setType("video/*");
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        addMultipleSelectionFlag(intent);
+        launchPhotoPickerForIntent(intent);
 
         final List<UiObject> itemList = findItemList(videoCount);
         final int itemCount = itemList.size();
         assertThat(itemCount).isEqualTo(videoCount);
 
         // Select one item from Photo grid
-        clickAndWait(itemList.get(0));
+        clickAndWait(mDevice, itemList.get(0));
 
         // Preview the item
         UiObject item = itemList.get(1);
@@ -289,14 +255,14 @@
                 .that(addOrSelectButton.waitForExists(1000)).isTrue();
 
         // Select the item from Preview
-        clickAndWait(addOrSelectButton);
+        clickAndWait(mDevice, addOrSelectButton);
 
         mDevice.pressBack();
 
         // Select one more item from Photo grid
-        clickAndWait(itemList.get(2));
+        clickAndWait(mDevice, itemList.get(2));
 
-        clickAndWait(findAddButton());
+        clickAndWait(mDevice, findAddButton());
 
         // Verify that all 3 items are returned
         final ClipData clipData = mActivity.getResult().data.getClipData();
@@ -313,19 +279,20 @@
     @Test
     public void testMultiSelect_preview() throws Exception {
         final int imageCount = 4;
-        createImages(imageCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+
+        Intent intent = new Intent(mAction);
+        addMultipleSelectionFlag(intent);
+        launchPhotoPickerForIntent(intent);
 
         final List<UiObject> itemList = findItemList(imageCount);
         final int itemCount = itemList.size();
         assertThat(itemCount).isEqualTo(imageCount);
         for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
+            clickAndWait(mDevice, itemList.get(i));
         }
 
-        clickAndWait(findViewSelectedButton());
+        clickAndWait(mDevice, findViewSelectedButton());
 
         // Swipe left three times
         swipeLeftAndWait();
@@ -333,10 +300,10 @@
         swipeLeftAndWait();
 
         // Deselect one item
-        clickAndWait(findPreviewSelectedCheckButton());
+        clickAndWait(mDevice, findPreviewSelectedCheckButton());
 
         // Return selected items
-        clickAndWait(findPreviewAddButton());
+        clickAndWait(mDevice, findPreviewAddButton());
 
         final ClipData clipData = mActivity.getResult().data.getClipData();
         final int count = clipData.getItemCount();
@@ -383,20 +350,31 @@
 
         // Test 2: Click Mute Button
         // Click to unmute the audio
-        clickAndWait(muteButton);
+        clickAndWait(mDevice, muteButton);
+
+        waitForBinderCallsToComplete();
+
         // Check that mute button state is unmute, i.e., it shows `volume up` icon
         assertMuteButtonState(muteButton, /* isMuted */ false);
         // Click on the muteButton and check that mute button status is now 'mute'
-        clickAndWait(muteButton);
+        clickAndWait(mDevice, muteButton);
+
+        waitForBinderCallsToComplete();
+
         assertMuteButtonState(muteButton, /* isMuted */ true);
         // Click on the muteButton and check that mute button status is now unmute
-        clickAndWait(muteButton);
+        clickAndWait(mDevice, muteButton);
+
+        waitForBinderCallsToComplete();
+
         assertMuteButtonState(muteButton, /* isMuted */ false);
 
         // Test 3: Next preview resumes mute state
         // Go back and launch preview again
         mDevice.pressBack();
-        clickAndWait(findViewSelectedButton());
+        clickAndWait(mDevice, findViewSelectedButton());
+
+        waitForBinderCallsToComplete();
 
         // check that player controls are visible
         assertPlayerControlsVisible(playPauseButton, muteButton);
@@ -421,16 +399,25 @@
         assertMuteButtonState(muteButton, /* isMuted */ true);
         // Swipe to next page and check that muteButton is in mute state.
         swipeLeftAndWait();
+
+        waitForBinderCallsToComplete();
+
         // set-up and wait for player controls to be sticky
         setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton);
         assertMuteButtonState(muteButton, /* isMuted */ true);
 
         // Test 2: Swipe resumes mute state, with state of mute button 'volume up' / 'unmute'
         // Click muteButton again to check the next video resumes the previous video's mute state
-        clickAndWait(muteButton);
+        clickAndWait(mDevice, muteButton);
+
+        waitForBinderCallsToComplete();
+
         assertMuteButtonState(muteButton, /* isMuted */ false);
         // check that next video resumed previous video's mute state
         swipeLeftAndWait();
+
+        waitForBinderCallsToComplete();
+
         // Wait for 1s before checking Play/Pause button's visibility
         playPauseButton.waitForExists(1000);
         // check that player controls are visible
@@ -442,6 +429,78 @@
     }
 
     @Test
+    public void testVideoPreviewAudioFocus() throws Exception {
+        final int[] focusStateForTest = new int[1];
+        final AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        AudioFocusRequest audioFocusRequest =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.USAGE_MEDIA)
+                        .setUsage(AudioAttributes.CONTENT_TYPE_MOVIE)
+                        .build())
+                .setWillPauseWhenDucked(true)
+                .setAcceptsDelayedFocusGain(false)
+                .setOnAudioFocusChangeListener(focusChange -> {
+                    if (focusChange == AudioManager.AUDIOFOCUS_LOSS
+                            || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
+                            || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+                        focusStateForTest[0] = focusChange;
+                    }
+                })
+                .build();
+
+        // Request AudioFocus
+        assertWithMessage("Expected requestAudioFocus result")
+                .that(audioManager.requestAudioFocus(audioFocusRequest))
+                .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+        // Launch Preview
+        launchPreviewMultipleWithVideos(/* videoCount */ 2);
+        // Video preview launches in mute mode, hence, test's audio focus shouldn't be lost when
+        // video preview starts
+        assertThat(focusStateForTest[0]).isEqualTo(0);
+
+        final UiObject muteButton = findMuteButton();
+        // unmute the audio of video preview
+        clickAndWait(mDevice, muteButton);
+
+        // Remote video preview involves binder calls
+        // Wait for Binder calls to complete and device to be idle
+        MediaStore.waitForIdle(mContext.getContentResolver());
+        mDevice.waitForIdle();
+
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+
+        // Verify that test lost the audio focus because PhotoPicker has requested audio focus now.
+        assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+        // Reset the focusStateForTest to verify test loses audio focus when video preview is
+        // launched with unmute state
+        focusStateForTest[0] = 0;
+        // Abandon the audio focus before requesting again. This is necessary to reduce test flakes
+        audioManager.abandonAudioFocusRequest(audioFocusRequest);
+        // Request AudioFocus from test again
+        assertWithMessage("Expected requestAudioFocus result")
+                .that(audioManager.requestAudioFocus(audioFocusRequest))
+                        .isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+        // Wait for PhotoPicker to lose Audio Focus
+        findPlayButton().waitForExists(SHORT_TIMEOUT);
+        // Test requesting audio focus will make PhotoPicker lose audio focus, Verify video is
+        // paused when PhotoPicker loses audio focus.
+        assertWithMessage("PlayPause button's content description")
+                .that(findPlayPauseButton().getContentDescription())
+                .isEqualTo("Play");
+
+        // Swipe to next video and verify preview gains audio focus
+        swipeLeftAndWait();
+        findPauseButton().waitForExists(SHORT_TIMEOUT);
+        // Video preview is now in unmute mode. Hence, PhotoPicker will request audio focus. Verify
+        // that test lost the audio focus.
+        assertThat(focusStateForTest[0]).isEqualTo(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+    }
+
+    @Test
     @Ignore("Re-enable once we find work around for b/226318844")
     public void testMultiSelect_previewVideoControlsVisibility() throws Exception {
         launchPreviewMultipleWithVideos(/* videoCount */ 3);
@@ -453,7 +512,7 @@
 
         final UiObject playerView = findPlayerView();
         // Click on StyledPlayerView to make the video controls visible
-        clickAndWait(playerView);
+        clickAndWait(mDevice, playerView);
         assertPlayerControlsVisible(playPauseButton, muteButton);
 
         // Wait for 1s and check that controls are still visible
@@ -470,7 +529,7 @@
         assertPlayerControlsHidden(playPauseButton, muteButton);
 
         // Click on the StyledPlayerView and check that controls appear
-        clickAndWait(playerView);
+        clickAndWait(mDevice, playerView);
         assertPlayerControlsVisible(playPauseButton, muteButton);
 
         // Swipe left to check that controls are now visible on swipe
@@ -487,26 +546,26 @@
     @Test
     public void testMimeTypeFilter() throws Exception {
         final int videoCount = 2;
-        createDNGVideos(videoCount, mContext.getUserId(), mUriList);
+        mUriList.addAll(createDNGVideosAndGetUris(videoCount, mContext.getUserId()));
         final int imageCount = 1;
-        createImages(imageCount, mContext.getUserId(), mUriList);
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
 
         final String mimeType = "video/dng";
 
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        Intent intent = new Intent(mAction);
+        addMultipleSelectionFlag(intent);
         intent.setType(mimeType);
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        launchPhotoPickerForIntent(intent);
 
         // find all items
         final List<UiObject> itemList = findItemList(-1);
         final int itemCount = itemList.size();
         assertThat(itemCount).isAtLeast(videoCount);
         for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
+            clickAndWait(mDevice, itemList.get(i));
         }
 
-        clickAndWait(findAddButton());
+        clickAndWait(mDevice, findAddButton());
 
         final ClipData clipData = mActivity.getResult().data.getClipData();
         final int count = clipData.getItemCount();
@@ -520,6 +579,89 @@
         }
     }
 
+    @Test
+    public void testExtraMimeTypeFilter() throws Exception {
+        final int dngVideoCount = 2;
+        // Creates 2 videos with mime type: "video/dng"
+        mUriList.addAll(createDNGVideosAndGetUris(dngVideoCount, mContext.getUserId()));
+
+        final int mp4VideoCount = 3;
+        // Creates 3 videos with mime type: "video/mp4"
+        mUriList.addAll(createVideosAndGetUris(mp4VideoCount, mContext.getUserId()));
+
+        final int imageCount = 4;
+        // Creates 4 images with mime type: "image/dng"
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+
+        Intent intent = new Intent(mAction);
+        addMultipleSelectionFlag(intent);
+
+        if (Intent.ACTION_GET_CONTENT.equals(intent.getAction())) {
+            intent.setType("*/*");
+        }
+        final String[] mimeTypes = new String[]{"video/dng", "image/dng"};
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
+        launchPhotoPickerForIntent(intent);
+
+        final int totalCount = dngVideoCount + imageCount;
+        final List<UiObject> itemList = findItemList(totalCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isAtLeast(totalCount);
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(mDevice, itemList.get(i));
+        }
+
+        clickAndWait(mDevice, findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        assertWithMessage("Expected number of items returned to be: " + itemCount)
+                .that(clipData.getItemCount()).isEqualTo(itemCount);
+        for (int i = 0; i < itemCount; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+            assertContainsMimeType(uri, mimeTypes);
+        }
+    }
+
+    @Test
+    public void testMimeTypeFilterPriority() throws Exception {
+        final int videoCount = 2;
+        mUriList.addAll(createDNGVideosAndGetUris(videoCount, mContext.getUserId()));
+        final int imageCount = 1;
+        mUriList.addAll(createImagesAndGetUris(imageCount, mContext.getUserId()));
+
+        Intent intent = new Intent(mAction);
+        addMultipleSelectionFlag(intent);
+        // setType has lower priority than EXTRA_MIME_TYPES filters.
+        intent.setType("image/*");
+        final String mimeType = "video/dng";
+        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {mimeType});
+        launchPhotoPickerForIntent(intent);
+
+        // find all items
+        final List<UiObject> itemList = findItemList(-1);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isAtLeast(videoCount);
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(mDevice, itemList.get(i));
+        }
+
+        clickAndWait(mDevice, findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        assertWithMessage("Expected number of items returned to be: " + itemCount)
+                .that(clipData.getItemCount()).isEqualTo(itemCount);
+        for (int i = 0; i < itemCount; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+            assertMimeType(uri, mimeType);
+        }
+    }
+
     private void assertMuteButtonState(UiObject muteButton, boolean isMuted)
             throws UiObjectNotFoundException {
         // We use content description to assert the state of the mute button, there is no other way
@@ -539,26 +681,27 @@
         assertPlayerControlsAutoHide(playPauseButton, muteButton);
 
         // Click on StyledPlayerView to make the video controls visible
-        clickAndWait(findPlayerView());
+        clickAndWait(mDevice, findPlayerView());
 
         // PlayPause button is now pause button, click the button to pause the video.
-        clickAndWait(playPauseButton);
+        clickAndWait(mDevice, playPauseButton);
 
         // Wait for 1s and check that play button is not auto hidden
         assertPlayerControlsDontAutoHide(playPauseButton, muteButton);
 
         // PlayPause button is now play button, click the button to play the video.
-        clickAndWait(playPauseButton);
+        clickAndWait(mDevice, playPauseButton);
         // Check that pause button auto-hides in 1s.
         assertPlayerControlsAutoHide(playPauseButton, muteButton);
     }
 
     private void launchPreviewMultipleWithVideos(int videoCount) throws  Exception {
-        createVideos(videoCount, mContext.getUserId(), mUriList);
-        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
-        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mUriList.addAll(createVideosAndGetUris(videoCount, mContext.getUserId()));
+
+        Intent intent = new Intent(mAction);
         intent.setType("video/*");
-        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        addMultipleSelectionFlag(intent);
+        launchPhotoPickerForIntent(intent);
 
         final List<UiObject> itemList = findItemList(videoCount);
         final int itemCount = itemList.size();
@@ -566,16 +709,20 @@
         assertThat(itemCount).isEqualTo(videoCount);
 
         for (int i = 0; i < itemCount; i++) {
-            clickAndWait(itemList.get(i));
+            clickAndWait(mDevice, itemList.get(i));
         }
 
-        clickAndWait(findViewSelectedButton());
+        clickAndWait(mDevice, findViewSelectedButton());
 
         // Wait for playback to start. This is needed in some devices where playback
         // buffering -> ready state takes around 10s.
         final long playbackStartTimeout = 10000;
         (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
 
+        waitForBinderCallsToComplete();
+    }
+
+    private void waitForBinderCallsToComplete() {
         // Wait for Binder calls to complete and device to be idle
         MediaStore.waitForIdle(mContext.getContentResolver());
         mDevice.waitForIdle();
@@ -588,7 +735,7 @@
         // Wait for 1s or Play/Pause button to hide
         playPauseButton.waitUntilGone(1000);
         // Click on StyledPlayerView to make the video controls visible
-        clickAndWait(playerView);
+        clickAndWait(mDevice, playerView);
         assertPlayerControlsVisible(playPauseButton, muteButton);
     }
 
@@ -650,20 +797,54 @@
                 REGEX_PACKAGE_NAME + ":id/exo_play_pause"));
     }
 
+    private static UiObject findPauseButton() {
+        return new UiObject(new UiSelector().descriptionContains("Pause"));
+    }
+
+    private static UiObject findPlayButton() {
+        return new UiObject(new UiSelector().descriptionContains("Play"));
+    }
+
     private static UiObject findPreviewVideoImageView() {
         return new UiObject(new UiSelector().resourceIdMatches(
                 REGEX_PACKAGE_NAME + ":id/preview_video_image"));
     }
 
-    private void clickAndWait(UiObject uiObject) throws Exception {
-        uiObject.click();
-        mDevice.waitForIdle();
-    }
-
     private void swipeLeftAndWait() {
         final int width = mDevice.getDisplayWidth();
         final int height = mDevice.getDisplayHeight();
         mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 10);
         mDevice.waitForIdle();
     }
+
+    private static List<String> getTestParameters() {
+        return Arrays.asList(
+                MediaStore.ACTION_PICK_IMAGES,
+                Intent.ACTION_GET_CONTENT
+        );
+    }
+
+    private void addMultipleSelectionFlag(Intent intent) {
+        switch (intent.getAction()) {
+            case MediaStore.ACTION_PICK_IMAGES:
+                intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX,
+                        MediaStore.getPickImagesMaxLimit());
+                break;
+            case Intent.ACTION_GET_CONTENT:
+                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                break;
+            default:
+                // do nothing
+        }
+    }
+
+    private void launchPhotoPickerForIntent(Intent intent) throws Exception {
+        // GET_CONTENT needs to have setType
+        if (Intent.ACTION_GET_CONTENT.equals(intent.getAction()) && intent.getType() == null) {
+            intent.setType("*/*");
+            intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
+        }
+
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+    }
 }
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/GetContentActivityAliasUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/GetContentActivityAliasUtils.java
new file mode 100644
index 0000000..0ccdd3f
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/GetContentActivityAliasUtils.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.photopicker.cts.util;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.provider.MediaStore;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+public class GetContentActivityAliasUtils {
+
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5);
+    private static final long POLLING_SLEEP_MILLIS = 100;
+
+    private static ComponentName sComponentName = new ComponentName(
+            getMediaProviderPackageName(),
+            "com.android.providers.media.photopicker.PhotoPickerGetContentActivity");
+
+    public static int enableAndGetOldState() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final PackageManager packageManager = inst.getContext().getPackageManager();
+        if (isComponentEnabledSetAsExpected(packageManager,
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
+            return PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        }
+
+        final int currentState = packageManager.getComponentEnabledSetting(sComponentName);
+
+        updateComponentEnabledSetting(packageManager,
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        return currentState;
+    }
+
+    public static void restoreState(int oldState) throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        updateComponentEnabledSetting(inst.getContext().getPackageManager(), oldState);
+    }
+
+    /**
+     * Clears the package data.
+     */
+    public static void clearPackageData(String packageName) throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand("pm clear " + packageName);
+
+        // We should ideally be listening to an effective measure to know if package data was
+        // cleared, like listening to a broadcasts or checking a value. But that information is
+        // very package private and not available.
+        Thread.sleep(500);
+    }
+
+    public static String getDocumentsUiPackageName() {
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+        intent.setType("*/*");
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final ResolveInfo ri = inst.getContext().getPackageManager().resolveActivity(intent, 0);
+        return ri.activityInfo.packageName;
+    }
+
+    private static void updateComponentEnabledSetting(PackageManager packageManager,
+            int state) throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        inst.getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+        try {
+            packageManager.setComponentEnabledSetting(sComponentName, state,
+                    PackageManager.DONT_KILL_APP);
+        } finally {
+            inst.getUiAutomation().dropShellPermissionIdentity();
+        }
+        waitForComponentToBeInExpectedState(packageManager, state);
+    }
+
+    private static void waitForComponentToBeInExpectedState(PackageManager packageManager,
+            int state) throws Exception {
+        pollForCondition(() -> isComponentEnabledSetAsExpected(packageManager, state),
+                "Timed out while waiting for component to be enabled");
+    }
+
+    private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
+            throws Exception {
+        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+            if (condition.get()) {
+                return;
+            }
+            Thread.sleep(POLLING_SLEEP_MILLIS);
+        }
+        throw new TimeoutException(errorMessage);
+    }
+
+    private static boolean isComponentEnabledSetAsExpected(PackageManager packageManager,
+            int state) {
+        return packageManager.getComponentEnabledSetting(sComponentName) == state;
+    }
+
+    private static String getMediaProviderPackageName() {
+        final Instrumentation inst = androidx.test.InstrumentationRegistry.getInstrumentation();
+        final PackageManager packageManager = inst.getContext().getPackageManager();
+        final ProviderInfo providerInfo = packageManager.resolveContentProvider(
+                MediaStore.AUTHORITY, PackageManager.MATCH_ALL);
+        return providerInfo.packageName;
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
index 9eb7d59..6d86cee 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -80,6 +80,12 @@
         assertThat(resultMimeType).isEqualTo(expectedMimeType);
     }
 
+    public static void assertContainsMimeType(Uri uri, String[] expectedMimeTypes) {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String resultMimeType = context.getContentResolver().getType(uri);
+        assertThat(Arrays.asList(expectedMimeTypes).contains(resultMimeType)).isTrue();
+    }
+
     public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
         assertThat(uri).isNotNull();
         final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
@@ -120,11 +126,7 @@
                     .that(xmp.contains("13166/7763")).isTrue();
         }
 
-        // assert no write access
-        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
-            fail("Does not grant write access to uri " + uri.toString());
-        } catch (SecurityException | FileNotFoundException expected) {
-        }
+        assertNoWriteAccess(uri, resolver);
     }
 
     private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
@@ -153,12 +155,7 @@
                 assertImageExifRedacted(is);
             }
 
-            // Assert no write access
-            try (ParcelFileDescriptor pfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)) {
-                fail("Does not grant write access to file " + file);
-            } catch (IOException e) {
-            }
+            assertNoWriteAccess(uri, resolver);
         }
     }
 
@@ -179,4 +176,19 @@
         assertWithMessage("Redacted non-location XMP")
                 .that(xmp.contains("LensDefaults")).isTrue();
     }
+
+    public static void assertReadOnlyAccess(Uri uri, ContentResolver resolver) throws Exception {
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) {
+            assertThat(pfd).isNotNull();
+        }
+
+        assertNoWriteAccess(uri, resolver);
+    }
+
+    private static void assertNoWriteAccess(Uri uri, ContentResolver resolver) throws Exception {
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+            fail("Does not grant write access to uri " + uri.toString());
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
 }
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
index 3705ddd..2732df7 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -26,6 +26,7 @@
 import android.provider.MediaStore;
 import android.provider.cts.ProviderTestUtils;
 import android.provider.cts.media.MediaStoreUtils;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -34,6 +35,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -42,24 +44,55 @@
 public class PhotoPickerFilesUtils {
     public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
 
-    public static void createImages(int count, int userId, List<Uri> uriList)
+    public static List<Uri> createImagesAndGetUris(int count, int userId)
             throws Exception {
-        createImages(count, userId, uriList, false);
+        return createImagesAndGetUris(count, userId, /* isFavorite */ false);
     }
 
-    public static void createImages(int count, int userId, List<Uri> uriList, boolean isFavorite)
+    public static List<Uri> createImagesAndGetUris(int count, int userId, boolean isFavorite)
             throws Exception {
+        List<Pair<Uri, String>> createdImagesData = createImagesAndGetUriAndPath(count, userId,
+                isFavorite);
+
+        List<Uri> uriList = new ArrayList<>();
+        for (Pair<Uri, String> createdImageData: createdImagesData) {
+            uriList.add(createdImageData.first);
+        }
+
+        return uriList;
+    }
+
+    /**
+     * Create multiple images according to the given parameters and returns the uris and filenames
+     * of the images created.
+     *
+     * @param count number of images to create
+     * @param userId user id to create images in
+     *
+     * @return list of data (uri and filename pair) about the images created
+     */
+    public static List<Pair<Uri, String>> createImagesAndGetUriAndPath(int count, int userId,
+            boolean isFavorite) throws Exception {
+        List<Pair<Uri, String>> createdImagesData = new ArrayList<>();
+
         for (int i = 0; i < count; i++) {
-            final Uri uri = createImage(userId, isFavorite);
-            uriList.add(uri);
-            clearMediaOwner(uri, userId);
+            Pair<Uri, String> createdImageData = createImage(userId, isFavorite);
+            createdImagesData.add(createdImageData);
+            clearMediaOwner(createdImageData.first, userId);
         }
         // Wait for Picker db sync to complete
         MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+
+        return createdImagesData;
     }
 
-    public static void createDNGVideos(int count, int userId, List<Uri> uriList)
-            throws Exception {
+    public static Pair<Uri, String> createImage(int userId, boolean isFavorite) throws Exception {
+        return getPermissionAndStageMedia(R.raw.lg_g4_iso_800_jpg,
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId, isFavorite);
+    }
+
+    public static List<Uri> createDNGVideosAndGetUris(int count, int userId) throws Exception {
+        List<Uri> uriList = new ArrayList<>();
         for (int i = 0; i < count; i++) {
             final Uri uri = createDNGVideo(userId);
             uriList.add(uri);
@@ -67,10 +100,12 @@
         }
         // Wait for Picker db sync to complete
         MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+
+        return uriList;
     }
 
-    public static void createVideos(int count, int userId, List<Uri> uriList)
-            throws Exception {
+    public static List<Uri> createVideosAndGetUris(int count, int userId) throws Exception {
+        List<Uri> uriList = new ArrayList<>();
         for (int i = 0; i < count; i++) {
             final Uri uri = createVideo(userId);
             uriList.add(uri);
@@ -78,6 +113,8 @@
         }
         // Wait for Picker db sync to complete
         MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+
+        return uriList;
     }
 
     public static void deleteMedia(Uri uri, Context context) throws Exception {
@@ -95,26 +132,19 @@
     }
 
     private static Uri createDNGVideo(int userId) throws Exception {
-        final Uri uri = stageMedia(R.raw.test_video_dng,
-                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
-        return uri;
+        return getPermissionAndStageMedia(R.raw.test_video_dng,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId,
+                /* isFavorite */ false).first;
     }
 
     private static Uri createVideo(int userId) throws Exception {
-        final Uri uri = stageMedia(R.raw.test_video,
-                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
-        return uri;
+        return getPermissionAndStageMedia(R.raw.test_video,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId,
+                /* isFavorite */ false).first;
     }
 
-    private static Uri createImage(int userId, boolean isFavorite) throws Exception {
-        final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
-                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId, isFavorite);
-        return uri;
-    }
-
-    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId,
-            boolean isFavorite) throws
-            Exception {
+    private static Pair<Uri, String> getPermissionAndStageMedia(int resId, Uri collectionUri,
+            String mimeType, int userId, boolean isFavorite) throws Exception {
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(
                 android.Manifest.permission.INTERACT_ACROSS_USERS,
@@ -130,14 +160,8 @@
         }
     }
 
-    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
-            Exception {
-        return stageMedia(resId, collectionUri, mimeType, userId, false);
-    }
-
-    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context,
-            boolean isFavorite)
-            throws IOException {
+    private static Pair<Uri, String> stageMedia(int resId, Uri collectionUri, String mimeType,
+            Context context, boolean isFavorite) throws IOException {
         final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
         final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
                 collectionUri, displayName, mimeType);
@@ -149,7 +173,7 @@
                  OutputStream target = session.openOutputStream()) {
                 FileUtils.copy(source, target);
             }
-            return session.publish();
+            return new Pair(session.publish(), displayName);
         }
     }
 }
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
index d20dcd6..8f58f3e 100644
--- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -20,8 +20,8 @@
 
 import android.text.format.DateUtils;
 
+import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject;
-import androidx.test.uiautomator.UiObjectNotFoundException;
 import androidx.test.uiautomator.UiScrollable;
 import androidx.test.uiautomator.UiSelector;
 
@@ -35,6 +35,7 @@
     public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
 
     private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+
     public static final String REGEX_PACKAGE_NAME =
             "com(.google)?.android.providers.media(.module)?";
 
@@ -92,4 +93,22 @@
         return new UiObject(new UiSelector().resourceIdMatches(
                 REGEX_PACKAGE_NAME + ":id/profile_button"));
     }
+
+    public static void findAndClickBrowse(UiDevice uiDevice) throws Exception {
+        assertWithMessage("Timed out waiting for overflow menu to appear")
+                .that(new UiObject(new UiSelector().description("More options"))
+                        .waitForExists(SHORT_TIMEOUT))
+                .isTrue();
+
+        final UiObject overflowMenu = new UiObject(new UiSelector().description("More options"));
+        clickAndWait(uiDevice, overflowMenu);
+
+        final UiObject browseButton = new UiObject(new UiSelector().textContains("Browse"));
+        clickAndWait(uiDevice, browseButton);
+    }
+
+    public static void clickAndWait(UiDevice uiDevice, UiObject uiObject) throws Exception {
+        uiObject.click();
+        uiDevice.waitForIdle();
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
index 70b68b4..affdaa8 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
@@ -676,6 +676,7 @@
                                         .build())
                         .build();
         mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(schema).build()).get();
+
         // Creates a large batch of Documents, since we have max document size in Framework which is
         // 512KiB, we will create 1KiB * 4000 docs = 4MiB total size > 1MiB binder transaction limit
         char[] chars = new char[1024]; // 1KiB
@@ -3851,4 +3852,104 @@
         documents = convertSearchResultsToDocuments(searchResults);
         assertThat(documents).containsExactly(inEmail1);
     }
+
+    @Test
+    public void testSetSchemaWithIncompatibleNestedSchema() throws Exception {
+        // 1. Set the original schema. This should succeed without any problems.
+        AppSearchSchema originalNestedSchema =
+                new AppSearchSchema.Builder("TypeA")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("prop1")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .build())
+                        .build();
+        SetSchemaRequest originalRequest =
+                new SetSchemaRequest.Builder().addSchemas(originalNestedSchema).build();
+        mDb1.setSchemaAsync(originalRequest).get();
+
+        // 2. Set a new schema with a new type that refers to "TypeA" and an incompatible change to
+        // "TypeA". This should fail.
+        AppSearchSchema newNestedSchema =
+                new AppSearchSchema.Builder("TypeA")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("prop1")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .build())
+                        .build();
+        AppSearchSchema newSchema =
+                new AppSearchSchema.Builder("TypeB")
+                        .addProperty(
+                                new AppSearchSchema.DocumentPropertyConfig.Builder("prop2", "TypeA")
+                                        .build())
+                        .build();
+        final SetSchemaRequest newRequest =
+                new SetSchemaRequest.Builder().addSchemas(newNestedSchema, newSchema).build();
+        Throwable throwable =
+                assertThrows(ExecutionException.class, () -> mDb1.setSchemaAsync(newRequest).get())
+                        .getCause();
+        assertThat(throwable).isInstanceOf(AppSearchException.class);
+        AppSearchException exception = (AppSearchException) throwable;
+        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+        assertThat(exception).hasMessageThat().contains("Incompatible types: {TypeA}");
+
+        // 3. Now set that same set of schemas but with forceOverride=true. This should succeed.
+        SetSchemaRequest newRequestForced =
+                new SetSchemaRequest.Builder()
+                        .addSchemas(newNestedSchema, newSchema)
+                        .setForceOverride(true)
+                        .build();
+        mDb1.setSchemaAsync(newRequestForced).get();
+    }
+
+    @Test
+    public void testEmojiSnippet() throws Exception {
+        // Schema registration
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // String:     "Luca Brasi sleeps with the 🐟🐟🐟."
+        //              ^    ^     ^      ^    ^   ^ ^  ^ ^
+        // UTF8 idx:    0    5     11     18   23 27 3135 39
+        // UTF16 idx:   0    5     11     18   23 27 2931 33
+        // Breaks into segments: "Luca", "Brasi", "sleeps", "with", "the", "🐟", "🐟"
+        // and "🐟".
+        // Index a document to instance 1.
+        String sicilianMessage = "Luca Brasi sleeps with the 🐟🐟🐟.";
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("namespace", "uri1").setBody(sicilianMessage).build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
+
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("namespace", "uri2")
+                        .setBody("Some other content.")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+
+        // Query for "🐟"
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "🐟",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .setSnippetCount(1)
+                                .setSnippetCountPerProperty(1)
+                                .build());
+        List<SearchResult> page = searchResults.getNextPageAsync().get();
+        assertThat(page).hasSize(1);
+        assertThat(page.get(0).getGenericDocument()).isEqualTo(inEmail1);
+        List<SearchResult.MatchInfo> matches = page.get(0).getMatchInfos();
+        assertThat(matches).hasSize(1);
+        assertThat(matches.get(0).getPropertyPath()).isEqualTo("body");
+        assertThat(matches.get(0).getFullText()).isEqualTo(sicilianMessage);
+        assertThat(matches.get(0).getExactMatch()).isEqualTo("🐟");
+        if (mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
+            assertThat(matches.get(0).getSubmatch()).isEqualTo("🐟");
+        }
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
index 5dd2eac..0bfa693 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
@@ -576,6 +576,30 @@
     }
 
     @Test
+    public void testNestedProperties_buildBlankPaths() {
+        Exception e =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                new GenericDocument.Builder<>("namespace", "id1", "schema1")
+                                        .setPropertyString("", "foo"));
+        assertThat(e.getMessage()).isEqualTo("Property name cannot be blank.");
+
+        e =
+                assertThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                new GenericDocument.Builder<>("namespace", "id1", "schema1")
+                                        .setPropertyDocument(
+                                                "propDoc",
+                                                new GenericDocument.Builder<>(
+                                                                "namespace", "id2", "schema1")
+                                                        .setPropertyString("", "Bat", "Hawk")
+                                                        .build()));
+        assertThat(e.getMessage()).isEqualTo("Property name cannot be blank.");
+    }
+
+    @Test
     public void testNestedProperties_invalidPaths() {
         GenericDocument doc =
                 new GenericDocument.Builder<>("namespace", "id1", "schema1")
@@ -592,25 +616,28 @@
                                         .build())
                         .build();
 
-        // Some paths are invalid because they don't apply to the given document --- these should
+        // These paths are invalid because they don't apply to the given document --- these should
         // return null. It's not the querier's fault.
         assertThat(doc.getPropertyStringArray("propString.propInts")).isNull();
         assertThat(doc.getPropertyStringArray("propDocs.propFoo")).isNull();
         assertThat(doc.getPropertyStringArray("propDocs.propNestedString.propFoo")).isNull();
+    }
 
-        // Some paths are invalid because they are malformed. These throw an exception --- the
-        // querier shouldn't provide such paths.
-        assertThrows(
-                IllegalArgumentException.class, () -> doc.getPropertyStringArray("propString[0"));
-        assertThrows(
-                IllegalArgumentException.class, () -> doc.getPropertyStringArray("propString[0.]"));
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> doc.getPropertyStringArray("propString[banana]"));
-        assertThrows(
-                IllegalArgumentException.class, () -> doc.getPropertyStringArray("propString[-1]"));
-        assertThrows(
-                IllegalArgumentException.class, () -> doc.getPropertyStringArray("propDocs[0]cat"));
+    @Test
+    public void testNestedProperties_arrayTypesInvalidPath() {
+        GenericDocument doc = new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyString("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyDocument("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyBoolean("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyDouble("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyLong("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyBytes("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyStringArray("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyDocumentArray("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyBooleanArray("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyDoubleArray("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyLongArray("."));
+        assertThrows(IllegalArgumentException.class, () -> doc.getPropertyBytesArray("."));
     }
 
     @Test
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
index 71822ca..47337ca 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
@@ -1641,6 +1641,11 @@
 
     @Test
     public void testAddObserver_schemaChange_added() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(
+                                Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
+
         // Register an observer
         TestObserverCallback observer = new TestObserverCallback();
         mGlobalSearchSession.registerObserverCallback(
@@ -1688,6 +1693,11 @@
 
     @Test
     public void testAddObserver_schemaChange_removed() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(
+                                Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
+
         // Add a schema type
         mDb1.setSchemaAsync(
                         new SetSchemaRequest.Builder()
@@ -1723,6 +1733,11 @@
 
     @Test
     public void testAddObserver_schemaChange_contents() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(
+                                Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
+
         // Add a schema
         mDb1.setSchemaAsync(
                         new SetSchemaRequest.Builder()
@@ -1794,6 +1809,11 @@
 
     @Test
     public void testAddObserver_schemaChange_contents_skipBySpec() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(
+                                Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
+
         // Add a schema
         mDb1.setSchemaAsync(
                         new SetSchemaRequest.Builder()
@@ -1862,6 +1882,11 @@
 
     @Test
     public void testRegisterObserver_schemaMigration() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(
+                                Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
+
         // Add a schema with two types
         mDb1.setSchemaAsync(
                         new SetSchemaRequest.Builder()
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 51033bb..bb83db1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -64,7 +64,6 @@
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.WindowUtil;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -84,13 +83,11 @@
 
     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/media/src/android/mediav2/cts/CodecInfoTest.java b/tests/media/src/android/mediav2/cts/CodecInfoTest.java
index c3a1b09..e38094c 100644
--- a/tests/media/src/android/mediav2/cts/CodecInfoTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecInfoTest.java
@@ -171,7 +171,8 @@
         // 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) {
+        if (FIRST_SDK_IS_AT_LEAST_T && 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 + "," +
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index 6065c5c..9d1d839 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -30,7 +30,9 @@
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.media.MediaMuxer;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -590,17 +592,12 @@
 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
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU);
     public static final boolean FIRST_SDK_IS_AT_LEAST_T =
-            ApiLevelUtil.isFirstApiAfter(Build.VERSION_CODES.S_V2);
+            ApiLevelUtil.isFirstApiAtLeast(Build.VERSION_CODES.TIRAMISU);
     public static final boolean VNDK_IS_AT_LEAST_T =
-            SystemProperties.getInt("ro.vndk.version", 0) > Build.VERSION_CODES.S_V2;
+            SystemProperties.getInt("ro.vndk.version", 0) >= Build.VERSION_CODES.TIRAMISU;
     public static final boolean IS_HDR_EDITING_SUPPORTED = isHDREditingSupported();
     private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
     enum SupportClass {
@@ -609,30 +606,14 @@
         CODEC_DEFAULT, // Default codec must support
         CODEC_OPTIONAL // Codec support is optional
     }
+
+    static final ArrayList<String> HDR_INFO_IN_BITSTREAM_CODECS = new ArrayList<>();
     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;
+            "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";
+    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";
+    static final HashMap<Integer, String> HDR_DYNAMIC_INFO = new HashMap<>();
+    static final HashMap<Integer, String> HDR_DYNAMIC_INCORRECT_INFO = new HashMap<>();
     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";
@@ -788,6 +769,45 @@
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILES);
         mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILES);
         mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC, AAC_PROFILES);
+
+        HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+        HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        HDR_INFO_IN_BITSTREAM_CODECS.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+
+        HDR_DYNAMIC_INFO.put(0, "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");
+        HDR_DYNAMIC_INFO.put(4, "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");
+        HDR_DYNAMIC_INFO.put(12, "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");
+        HDR_DYNAMIC_INFO.put(22, "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");
+
+
+        HDR_DYNAMIC_INCORRECT_INFO.put(0, "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");
+        HDR_DYNAMIC_INCORRECT_INFO.put(4, "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 01");
+        HDR_DYNAMIC_INCORRECT_INFO.put(12, "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 02");
+        HDR_DYNAMIC_INCORRECT_INFO.put(22, "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 03");
     }
 
     static int[] combine(int[] first, int[] second) {
@@ -1366,6 +1386,12 @@
         return Arrays.copyOfRange(tempArray, 0, i);
     }
 
+    void insertHdrDynamicInfo(byte[] info) {
+        final Bundle params = new Bundle();
+        params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
+        mCodec.setParameters(params);
+    }
+
     boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
         if (inpFormat == null || outFormat == null) return false;
         String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
@@ -1424,41 +1450,26 @@
         }
     }
 
-    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)) {
+    void validateHDRInfo(MediaFormat fmt, String hdrInfoKey, ByteBuffer hdrInfoRef) {
+        ByteBuffer hdrInfo = fmt.getByteBuffer(hdrInfoKey, null);
+        assertNotNull("No " + hdrInfoKey + " present in format : " + fmt, hdrInfo);
+        if (!hdrInfoRef.equals(hdrInfo)) {
             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++) {
+            byte[] ref = new byte[hdrInfoRef.capacity()];
+            hdrInfoRef.get(ref);
+            hdrInfoRef.rewind();
+            byte[] test = new byte[hdrInfo.capacity()];
+            hdrInfo.get(test);
+            hdrInfo.rewind();
+            for (int i = 0; i < ref.length; i++) {
                 refString.append(String.format("%2x ", ref[i]));
+            }
+            for (int i = 0; i < test.length; 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);
+            fail(hdrInfoKey + " mismatch in codec " + mCodecName + "\nref info : " + refString +
+                    "\n test info : " + testString);
         }
     }
 
@@ -1680,15 +1691,8 @@
                 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;
         }
@@ -1808,57 +1812,18 @@
         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 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 = "bbb_2ch_44kHz_s16le.raw";
+    protected 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;
+    protected final int INP_FRM_WIDTH = 352;
+    protected final int INP_FRM_HEIGHT = 288;
 
     final String mMime;
     final int[] mBitrates;
@@ -2217,3 +2182,241 @@
         return cdtb.mOutputBuff.getBuffer();
     }
 }
+
+class HDRDecoderTestBase extends CodecDecoderTestBase {
+    private static final String LOG_TAG = HDRDecoderTestBase.class.getSimpleName();
+
+    private ByteBuffer mHdrStaticInfoRef;
+    private ByteBuffer mHdrStaticInfoStream;
+    private ByteBuffer mHdrStaticInfoContainer;
+    private Map<Integer, String> mHdrDynamicInfoRef;
+    private Map<Integer, String> mHdrDynamicInfoStream;
+    private Map<Integer, String> mHdrDynamicInfoContainer;
+    private String mHdrDynamicInfoCurrent;
+
+    public HDRDecoderTestBase(String decoder, String mime, String testFile) {
+        super(decoder, mime, testFile);
+    }
+
+    void enqueueInput(int bufferIndex) {
+        if (mHdrDynamicInfoContainer != null && mHdrDynamicInfoContainer.containsKey(mInputCount) &&
+                mExtractor.getSampleSize() != -1) {
+            insertHdrDynamicInfo(
+                    loadByteArrayFromString(mHdrDynamicInfoContainer.get(mInputCount)));
+        }
+        super.enqueueInput(bufferIndex);
+    }
+
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if (info.size > 0 && mHdrDynamicInfoRef != null) {
+            MediaFormat format = mCodec.getOutputFormat(bufferIndex);
+            if (mHdrDynamicInfoRef.containsKey(mOutputCount)) {
+                mHdrDynamicInfoCurrent = mHdrDynamicInfoRef.get(mOutputCount);
+            }
+            validateHDRInfo(format, MediaFormat.KEY_HDR10_PLUS_INFO,
+                    ByteBuffer.wrap(loadByteArrayFromString(mHdrDynamicInfoCurrent)));
+        }
+        super.dequeueOutput(bufferIndex, info);
+    }
+
+    void validateHDRInfo(String hdrStaticInfoStream, String hdrStaticInfoContainer,
+                         Map<Integer, String> hdrDynamicInfoStream,
+                         Map<Integer, String> hdrDynamicInfoContainer) throws IOException,
+            InterruptedException {
+        mHdrStaticInfoStream = hdrStaticInfoStream != null ?
+                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoStream)) : null;
+        mHdrStaticInfoContainer = hdrStaticInfoContainer != null ?
+                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoContainer)) : null;
+        mHdrStaticInfoRef = mHdrStaticInfoStream == null ? mHdrStaticInfoContainer :
+                mHdrStaticInfoStream;
+        mHdrDynamicInfoStream = hdrDynamicInfoStream;
+        mHdrDynamicInfoContainer = hdrDynamicInfoContainer;
+        mHdrDynamicInfoRef = hdrDynamicInfoStream == null ? hdrDynamicInfoContainer :
+                hdrDynamicInfoStream;
+
+        assertTrue("reference hdr10/hdr10+ info is not supplied for validation",
+                mHdrDynamicInfoRef != null || mHdrStaticInfoRef != null);
+
+        if (mHdrDynamicInfoStream != null || mHdrDynamicInfoContainer != null) {
+            Assume.assumeNotNull("Test is only applicable to codecs that have HDR10+ profiles",
+                    mProfileHdr10PlusMap.get(mMime));
+        }
+        if (mHdrStaticInfoStream != null || mHdrStaticInfoContainer != null) {
+            Assume.assumeNotNull("Test is only applicable to codecs that have HDR10 profiles",
+                    mProfileHdr10Map.get(mMime));
+        }
+
+        File fObj = new File(mTestFile);
+        String parent = fObj.getParent();
+        if (parent != null) parent += File.separator;
+        else parent = mInpPrefix;
+        Preconditions.assertTestFileExists(parent + fObj.getName());
+        // For decoders, if you intend to supply hdr10+ info using external means like json, make
+        // sure that info that is being supplied is in sync with SEI info
+        if (mHdrDynamicInfoStream != null && mHdrDynamicInfoContainer != null) {
+            assertEquals("Container hdr10+ info size and elementary stream SEI hdr10+ " +
+                            "info size are unequal", mHdrDynamicInfoStream.size(),
+                    mHdrDynamicInfoContainer.size());
+            for (Map.Entry<Integer, String> element : mHdrDynamicInfoStream.entrySet()) {
+                assertTrue("Container hdr10+ info and elementary stream SEI hdr10+ " +
+                                "info frame positions are not in sync",
+                        mHdrDynamicInfoContainer.containsKey(element.getKey()));
+            }
+        }
+        mOutputBuff = new OutputManager();
+        MediaFormat format = setUpSource(parent, fObj.getName());
+        if (mHdrDynamicInfoStream != null || mHdrDynamicInfoContainer != null) {
+            format.setInteger(MediaFormat.KEY_PROFILE, mProfileHdr10PlusMap.get(mMime)[0]);
+        } else {
+            format.setInteger(MediaFormat.KEY_PROFILE, mProfileHdr10Map.get(mMime)[0]);
+        }
+        ArrayList<MediaFormat> formatList = new ArrayList<>();
+        formatList.add(format);
+        Assume.assumeTrue(mCodecName + " does not support HDR10/HDR10+ profile",
+                areFormatsSupported(mCodecName, mMime, formatList));
+        mCodec = MediaCodec.createByCodecName(mCodecName);
+        configureCodec(format, false, true, false);
+        mCodec.start();
+        doWork(Integer.MAX_VALUE);
+        queueEOS();
+        waitForAllOutputs();
+        if (mHdrStaticInfoRef != null) {
+            validateHDRInfo(mCodec.getOutputFormat(), MediaFormat.KEY_HDR_STATIC_INFO,
+                    mHdrStaticInfoRef);
+        }
+        mCodec.stop();
+        mCodec.release();
+        mExtractor.release();
+    }
+}
+
+class HDREncoderTestBase extends CodecEncoderTestBase {
+    private static final String LOG_TAG = HDREncoderTestBase.class.getSimpleName();
+
+    private ByteBuffer mHdrStaticInfo;
+    private Map<Integer, String> mHdrDynamicInfo;
+
+    private MediaMuxer mMuxer;
+    private int mTrackID = -1;
+
+    public HDREncoderTestBase(String encoderName, String mediaType, int bitrate, int width,
+                              int height) {
+        super(encoderName, mediaType, new int[]{bitrate}, new int[]{width}, new int[]{height});
+    }
+
+    void enqueueInput(int bufferIndex) {
+        if (mHdrDynamicInfo != null && mHdrDynamicInfo.containsKey(mInputCount)) {
+            insertHdrDynamicInfo(loadByteArrayFromString(mHdrDynamicInfo.get(mInputCount)));
+        }
+        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);
+    }
+
+    void validateHDRInfo(String hdrStaticInfo, Map<Integer, String> hdrDynamicInfo)
+            throws IOException, InterruptedException {
+        mHdrStaticInfo = hdrStaticInfo != null ?
+                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfo)) : null;
+        mHdrDynamicInfo = hdrDynamicInfo;
+
+        setUpParams(1);
+
+        MediaFormat format = mFormats.get(0);
+        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);
+        int profile = (mHdrDynamicInfo != null) ? mProfileHdr10PlusMap.get(mMime)[0] :
+                mProfileHdr10Map.get(mMime)[0];
+        format.setInteger(MediaFormat.KEY_PROFILE, profile);
+
+        if (mHdrStaticInfo != null) {
+            format.setByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, mHdrStaticInfo);
+        }
+        Assume.assumeTrue(mCodecName + " does not support HDR10/HDR10+ profile " + 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);
+
+        int frameLimit = 4;
+        if (mHdrDynamicInfo != null) {
+            Integer lastHdr10PlusFrame =
+                    Collections.max(HDR_DYNAMIC_INFO.entrySet(), Map.Entry.comparingByKey())
+                            .getKey();
+            frameLimit = lastHdr10PlusFrame + 10;
+        }
+        int maxNumFrames = mInputData.length / (INP_FRM_WIDTH * INP_FRM_HEIGHT * mBytesPerSample);
+        assertTrue("HDR info tests require input file with at least " + frameLimit +
+                        " frames. " + INPUT_VIDEO_FILE_HBD + " has " + maxNumFrames + " frames.",
+                frameLimit <= maxNumFrames);
+
+        mOutputBuff = new OutputManager();
+        mCodec = MediaCodec.createByCodecName(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(frameLimit);
+        queueEOS();
+        waitForAllOutputs();
+        if (mTrackID != -1) {
+            mMuxer.stop();
+            mTrackID = -1;
+        }
+        if (mMuxer != null) {
+            mMuxer.release();
+            mMuxer = null;
+        }
+        String log = String.format("format: %s \n codec: %s:: ", format, mCodecName);
+        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();
+        if (mHdrStaticInfo != null) {
+            // verify if the out fmt contains HDR Static info as expected
+            validateHDRInfo(fmt, MediaFormat.KEY_HDR_STATIC_INFO, mHdrStaticInfo);
+        }
+
+        // verify if the muxed file contains HDR Dynamic info as expected
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        String decoder = codecList.findDecoderForFormat(format);
+        assertNotNull("Device advertises support for encoding " + format + " but not decoding it",
+                decoder);
+
+        HDRDecoderTestBase decoderTest = new HDRDecoderTestBase(decoder, mMime,
+                tmpFile.getAbsolutePath());
+        decoderTest.validateHDRInfo(hdrStaticInfo, hdrStaticInfo, mHdrDynamicInfo, mHdrDynamicInfo);
+        if (HDR_INFO_IN_BITSTREAM_CODECS.contains(mMime)) {
+            decoderTest.validateHDRInfo(hdrStaticInfo, null, mHdrDynamicInfo, null);
+        }
+        tmpFile.delete();
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java b/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java
index 3dd28aa..4994a9b 100644
--- a/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java
+++ b/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java
@@ -22,6 +22,8 @@
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.CddTest;
+
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,34 +31,34 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
- * Test to validate hdr static metadata in decoders
+ * Test to validate hdr static info 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 {
+public class DecoderHDRInfoTest extends HDRDecoderTestBase {
     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;
+    private String mHDRStaticInfoStream;
+    private String mHDRStaticInfoContainer;
+    private Map<Integer, String> mHDRDynamicInfoStream;
+    private Map<Integer, String> mHDRDynamicInfoContainer;
 
     public DecoderHDRInfoTest(String codecName, String mediaType, String testFile,
-                              String hdrStaticInfoStream, String hdrStaticInfoContainer) {
+                              String hdrStaticInfoStream, String hdrStaticInfoContainer,
+                              Map<Integer, String> HDRDynamicInfoStream,
+                              Map<Integer, String> HDRDynamicInfoContainer) {
         super(codecName, mediaType, testFile);
-        mHDRStaticInfoStream = hdrStaticInfoStream != null ?
-                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoStream)) : null;
-        mHDRStaticInfoContainer = hdrStaticInfoContainer != null ?
-                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoContainer)) : null;
+        mHDRStaticInfoStream = hdrStaticInfoStream;
+        mHDRStaticInfoContainer = hdrStaticInfoContainer;
+        mHDRDynamicInfoStream = HDRDynamicInfoStream;
+        mHDRDynamicInfoContainer = HDRDynamicInfoContainer;
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -65,64 +67,50 @@
         final boolean needAudio = false;
         final boolean needVideo = true;
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
-                // codecMediaType, testFile, hdrInfo in stream, hdrInfo in container
+                // codecMediaType, testFile, hdrStaticInfo in stream, hdrStaticInfo in container,
+                // hdrDynamicInfo in stream, hdrDynamicInfo in container
                 {MediaFormat.MIMETYPE_VIDEO_HEVC,
                         "cosmat_352x288_hdr10_stream_and_container_correct_hevc.mkv",
-                        HDR_STATIC_INFO, HDR_STATIC_INFO},
+                        HDR_STATIC_INFO, HDR_STATIC_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC,
                         "cosmat_352x288_hdr10_stream_correct_container_incorrect_hevc.mkv",
-                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO},
+                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10_only_stream_hevc.mkv",
-                        HDR_STATIC_INFO, null},
+                        HDR_STATIC_INFO, null, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10_only_container_hevc.mkv",
-                        null, HDR_STATIC_INFO},
+                        null, HDR_STATIC_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_352x288_hdr10_only_container_vp9.mkv",
-                        null, HDR_STATIC_INFO},
+                        null, HDR_STATIC_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_AV1,
                         "cosmat_352x288_hdr10_stream_and_container_correct_av1.mkv",
-                        HDR_STATIC_INFO, HDR_STATIC_INFO},
+                        HDR_STATIC_INFO, HDR_STATIC_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_AV1,
                         "cosmat_352x288_hdr10_stream_correct_container_incorrect_av1.mkv",
-                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO},
+                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10_only_stream_av1.mkv",
-                        HDR_STATIC_INFO, null},
+                        HDR_STATIC_INFO, null, null, null},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10_only_container_av1.mkv",
-                        null, HDR_STATIC_INFO},
+                        null, HDR_STATIC_INFO, null, null},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10plus_hevc.mp4",
+                        null, null, HDR_DYNAMIC_INFO, null},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10plus_hevc.mp4",
+                        null, null, HDR_DYNAMIC_INFO, HDR_DYNAMIC_INCORRECT_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10plus_av1.mkv",
+                        null, null, HDR_DYNAMIC_INFO, null},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10plus_av1.mkv",
+                        null, null, HDR_DYNAMIC_INFO, HDR_DYNAMIC_INCORRECT_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_352x288_hdr10_only_container_vp9.mkv",
+                        null, null, null, HDR_DYNAMIC_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);
-            }
-        }
+    @CddTest(requirements = {"5.3.5/C-3-1", "5.3.7/C-4-1", "5.3.9"})
+    public void testHDRInfo() throws IOException, InterruptedException {
+        validateHDRInfo(mHDRStaticInfoStream, mHDRStaticInfoContainer, mHDRDynamicInfoStream,
+                mHDRDynamicInfoContainer);
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java b/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java
index 26c6eb5..66916e5 100644
--- a/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java
@@ -21,84 +21,59 @@
 import android.media.MediaFormat;
 import android.media.MediaMuxer;
 import android.os.Build;
-import android.os.Bundle;
+import android.util.Log;
 
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.After;
 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.Collections;
 import java.util.List;
+import java.util.Map;
 
 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
+ * HDR10 Metadata is an aid for a display device to show the content in an optimal manner. It
+ * contains the HDR content and mastering device properties that are used by the display device
+ * to map the content according to its own color gamut and peak brightness. This information is
+ * part of the elementary stream. Generally this information is placed at scene change intervals
+ * or even at every frame level. If the encoder is configured with hdr info, then it is
+ * expected to place this information in the elementary stream as-is. This test validates the
+ * same. The test feeds per-frame or per-scene info at various points and expects the encoder
+ * to place the hdr info in the elementary stream at exactly those points
+ *
+ * Restrict hdr info test for Android T and above
  */
 @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 {
+public class EncoderHDRInfoTest extends HDREncoderTestBase {
     private static final String LOG_TAG = EncoderHDRInfoTest.class.getSimpleName();
-    private MediaMuxer mMuxer;
-    private int mTrackID = -1;
 
-    static final ArrayList<String> mCheckESList = new ArrayList<>();
+    private String mHDRStaticInfo;
+    private Map<Integer, String> mHDRDynamicInfo;
 
-    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])));
-        }
+    public EncoderHDRInfoTest(String encoderName, String mediaType, int bitrate,
+                              int width, int height, String HDRStaticInfo,
+                              Map<Integer, String> HDRDynamicInfo) {
+        super(encoderName, mediaType, bitrate, width, height);
+        mHDRStaticInfo = HDRStaticInfo;
+        mHDRDynamicInfo = HDRDynamicInfo;
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -106,111 +81,28 @@
         final boolean isEncoder = true;
         final boolean needAudio = false;
         final boolean needVideo = true;
+        final String[] mediaTypes = new String[]{
+                MediaFormat.MIMETYPE_VIDEO_AV1,
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                MediaFormat.MIMETYPE_VIDEO_VP9
+        };
 
-        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},
-        });
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>();
+        for (String mediaType : mediaTypes) {
+            // mediaType, bitrate, width, height, hdrStaticInfo, hdrDynamicInfo
+            exhaustiveArgsList.add(new Object[]{mediaType, 512000, 352, 288, HDR_STATIC_INFO,
+                    null});
+            exhaustiveArgsList.add(new Object[]{mediaType, 512000, 352, 288, null,
+                    HDR_DYNAMIC_INFO});
+        }
 
         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();
+    @CddTest(requirements = {"5.12/C-6-4"})
+    public void testHDRInfo() throws IOException, InterruptedException {
+        validateHDRInfo(mHDRStaticInfo, mHDRDynamicInfo);
     }
 }
diff --git a/tests/ondevicepersonalization/TEST_MAPPING b/tests/ondevicepersonalization/TEST_MAPPING
new file mode 100644
index 0000000..8d96b25
--- /dev/null
+++ b/tests/ondevicepersonalization/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+        "name": "CtsOnDevicePersonalizationTestCases"
+    }
+  ]
+}
diff --git a/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java b/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
index 16db9a9..a0467a2 100644
--- a/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
+++ b/tests/ondevicepersonalization/src/android/ondevicepersonalization/cts/OnDevicePersonalizationServiceTest.java
@@ -39,7 +39,7 @@
     @Before
     public void setup() throws Exception {
         mContext = ApplicationProvider.getApplicationContext();
-        mService = new OnDevicePersonalizationManager(mContext);
+        mService = mContext.getSystemService(OnDevicePersonalizationManager.class);
     }
 
     @Test
diff --git a/tests/tests/bluetooth/AndroidTest.xml b/tests/tests/bluetooth/AndroidTest.xml
index 9a3075b..9818962 100644
--- a/tests/tests/bluetooth/AndroidTest.xml
+++ b/tests/tests/bluetooth/AndroidTest.xml
@@ -33,6 +33,6 @@
     <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
     <object type="module_controller"
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.bluetooth" />
+        <option name="mainline-module-package-name" value="com.android.btservices" />
     </object>
 </configuration>
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
index b2fbc6c..8acbbdd 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/ExtractDecodeEditEncodeMuxTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -42,6 +42,8 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -130,38 +132,43 @@
         super(MediaStubActivity.class);
     }
 
+    @CddTest(requirements = {"5.2", "5.3"})
     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);
+        setOutputVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
         TestWrapper.runTest(this);
     }
 
+    @CddTest(requirements = {"5.2", "5.3"})
     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);
+        setOutputVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
         TestWrapper.runTest(this);
     }
 
+    @CddTest(requirements = {"5.2", "5.3"})
     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);
+        setOutputVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
         TestWrapper.runTest(this);
     }
 
+    @CddTest(requirements = {"5.2", "5.3"})
     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);
+        setOutputVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC);
         TestWrapper.runTest(this);
     }
 
+    @CddTest(requirements = {"5.1.1", "5.1.2"})
     public void testExtractDecodeEditEncodeMuxAudio() throws Throwable {
         if(!setSize(1280, 720)) return;
         setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
@@ -170,11 +177,13 @@
         TestWrapper.runTest(this);
     }
 
+    @CddTest(requirements = {"5.1.1", "5.1.2", "5.2", "5.3"})
     public void testExtractDecodeEditEncodeMuxAudioVideo() throws Throwable {
         if(!setSize(1280, 720)) return;
         setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
         setCopyAudio();
         setCopyVideo();
+        setOutputVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
         setVerifyAudioFormat();
         TestWrapper.runTest(this);
     }
@@ -289,7 +298,7 @@
         mOutputFile = sb.toString();
     }
 
-    private void setVideoMimeType(String mimeType) {
+    private void setOutputVideoMimeType(String mimeType) {
         mOutputVideoMimeType = mimeType;
     }
 
@@ -305,44 +314,52 @@
 
         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);
+        String videoEncoderName = null;
+        MediaFormat outputVideoFormat = null;
+        if (mCopyVideo) {
+            // 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.
+            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);
+            // 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;
+            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);
         }
-        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 = null;
+        MediaFormat outputAudioFormat = null;
+        if (mCopyAudio) {
+            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;
+            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);
         }
-        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioEncoderName);
 
         MediaExtractor videoExtractor = null;
         MediaExtractor audioExtractor = null;
@@ -863,29 +880,25 @@
                 ByteBuffer decoderInputBuffer = audioDecoderInputBuffers[decoderInputBufferIndex];
                 int size = audioExtractor.readSampleData(decoderInputBuffer, 0);
                 long presentationTime = audioExtractor.getSampleTime();
+                int flags = audioExtractor.getSampleFlags();
                 if (VERBOSE) {
                     Log.d(TAG, "audio extractor: returned buffer of size " + size);
                     Log.d(TAG, "audio extractor: returned buffer for time " + presentationTime);
                 }
+                audioExtractorDone = !audioExtractor.advance();
+                if (audioExtractorDone) {
+                    if (VERBOSE) Log.d(TAG, "audio extractor: EOS");
+                    flags = flags | MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                }
                 if (size >= 0) {
                     audioDecoder.queueInputBuffer(
                             decoderInputBufferIndex,
                             0,
                             size,
                             presentationTime,
-                            audioExtractor.getSampleFlags());
+                            flags);
+                    audioExtractedFrameCount++;
                 }
-                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;
             }
diff --git a/tests/tests/media/common/src/android/media/cts/TestUtils.java b/tests/tests/media/common/src/android/media/cts/TestUtils.java
index f98b3ab..20bf143 100644
--- a/tests/tests/media/common/src/android/media/cts/TestUtils.java
+++ b/tests/tests/media/common/src/android/media/cts/TestUtils.java
@@ -190,6 +190,7 @@
         if (name.startsWith("c2.android.")) {
             return true;
         }
+        Log.d(TAG, "Test mode MTS does not test codec " + name);
         return false;
     }
 
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
index f5e3e0d..9b98fed 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/NativeDecoderTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/NativeDecoderTest.java
@@ -129,81 +129,6 @@
         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(
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
index 4afc7aa..74aa214 100644
--- a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
@@ -43,6 +43,7 @@
 import android.media.cts.OutputSurface;
 import android.media.cts.Preconditions;
 import android.media.cts.TestArgs;
+import android.media.cts.TestUtils;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
@@ -1301,6 +1302,10 @@
                 if (TestArgs.shouldSkipCodec(encoder)) {
                     continue;
                 }
+                if (!TestUtils.isTestableCodecInCurrentMode(encoder)) {
+                    Log.d(TAG, "Skipping tests for codec: " + encoder);
+                    continue;
+                }
                 CodecCapabilities caps = getCodecCapabities(encoder, mediaType, true);
                 assertNotNull(caps);
                 EncoderSize encoderSize = new EncoderSize(encoder, mediaType, caps);
diff --git a/tests/tests/media/misc/AndroidTest.xml b/tests/tests/media/misc/AndroidTest.xml
index 4a2ffaf..1facb8d 100644
--- a/tests/tests/media/misc/AndroidTest.xml
+++ b/tests/tests/media/misc/AndroidTest.xml
@@ -40,7 +40,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="CtsMediaMiscTestCases-1.0" />
+        <option name="media-folder-name" value="CtsMediaMiscTestCases-2.0" />
         <option name="dynamic-config-module" value="CtsMediaMiscTestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/media/misc/DynamicConfig.xml b/tests/tests/media/misc/DynamicConfig.xml
index 09e5fba..51e05bb 100644
--- a/tests/tests/media/misc/DynamicConfig.xml
+++ b/tests/tests/media/misc/DynamicConfig.xml
@@ -15,6 +15,6 @@
 
 <dynamicConfig>
     <entry key="media_files_url">
-    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/misc/CtsMediaMiscTestCases-1.0.zip</value>
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/misc/CtsMediaMiscTestCases-2.0.zip</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/tests/media/misc/copy_media.sh b/tests/tests/media/misc/copy_media.sh
index 96f5a49..27ba1e7 100755
--- a/tests/tests/media/misc/copy_media.sh
+++ b/tests/tests/media/misc/copy_media.sh
@@ -17,4 +17,4 @@
 [ -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"
+copy_media "misc" "CtsMediaMiscTestCases-2.0"
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
index 0c39229..dbb6035 100644
--- a/tests/tests/media/misc/src/android/media/misc/cts/WorkDir.java
+++ b/tests/tests/media/misc/src/android/media/misc/cts/WorkDir.java
@@ -20,6 +20,6 @@
 
 class WorkDir extends WorkDirBase {
     public static final String getMediaDirString() {
-        return getMediaDirString("CtsMediaMiscTestCases-1.0");
+        return getMediaDirString("CtsMediaMiscTestCases-2.0");
     }
 }
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
index 81c0d89..86c6912 100644
--- a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerFlakyNetworkTest.java
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerFlakyNetworkTest.java
@@ -75,6 +75,7 @@
     private CtsTestServer mServer;
 
     @Before
+    @Override
     public void setUp() throws Throwable {
         super.setUp();
     }
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/os/Android.bp b/tests/tests/os/Android.bp
index 8bc8222..c50c6a7 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -40,6 +40,7 @@
         "hamcrest-library",
         "modules-utils-build_system",
         "platformprotosnano",
+        "safety-center-internal-data",
     ],
     jni_uses_platform_apis: true,
     jni_libs: [
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
index b16ad15..f49e065 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
@@ -50,17 +50,18 @@
 import com.android.compatibility.common.util.click
 import com.android.compatibility.common.util.depthFirstSearch
 import com.android.compatibility.common.util.textAsString
+import java.io.InputStream
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.hamcrest.Matcher
 import org.hamcrest.Matchers
 import org.junit.Assert
 import org.junit.Assert.assertThat
 import org.junit.Assert.assertTrue
-import java.io.InputStream
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 private const val BROADCAST_TIMEOUT_MS = 60000L
 
+const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
 const val HIBERNATION_BOOT_RECEIVER_CLASS_NAME =
     "com.android.permissioncontroller.hibernation.HibernationOnBootReceiver"
 const val ACTION_SET_UP_HIBERNATION =
@@ -139,6 +140,14 @@
     }
 }
 
+fun runPermissionEventCleanupJob(context: Context) {
+    eventually {
+        runShellCommandOrThrow("cmd jobscheduler run -u " +
+            "${Process.myUserHandle().identifier} -f " +
+            "${context.packageManager.permissionControllerPackageName} 3")
+    }
+}
+
 inline fun withApp(
     apk: String,
     packageName: String,
@@ -196,6 +205,12 @@
         threshold.toString(), action)
 }
 
+inline fun <T> withSafetyCenterEnabled(action: () -> T): T {
+    return withDeviceConfig(
+        DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_SAFETY_CENTER_ENABLED,
+        true.toString(), action)
+}
+
 fun awaitAppState(pkg: String, stateMatcher: Matcher<Int>) {
     val context: Context = InstrumentationRegistry.getTargetContext()
     eventually {
@@ -218,6 +233,7 @@
 
 fun goHome() {
     runShellCommandOrThrow("input keyevent KEYCODE_HOME")
+    waitForIdle()
 }
 
 /**
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 9667a55..fdfda50 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -28,7 +28,11 @@
 import android.content.res.Resources
 import android.net.Uri
 import android.os.Build
+import android.os.UserHandle
 import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterManager
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiObject2
@@ -38,6 +42,7 @@
 import androidx.test.InstrumentationRegistry
 import androidx.test.filters.SdkSuppress
 import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
 import com.android.compatibility.common.util.DisableAnimationRule
 import com.android.compatibility.common.util.FreezeRotationRule
 import com.android.compatibility.common.util.MatcherUtils.hasTextThat
@@ -52,6 +57,13 @@
 import com.android.compatibility.common.util.depthFirstSearch
 import com.android.compatibility.common.util.uiDump
 import com.android.modules.utils.build.SdkLevel
+import com.android.safetycenter.internaldata.SafetyCenterIds
+import com.android.safetycenter.internaldata.SafetyCenterIssueId
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey
+import java.lang.reflect.Modifier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import java.util.regex.Pattern
 import org.hamcrest.CoreMatchers.containsString
 import org.hamcrest.CoreMatchers.containsStringIgnoringCase
 import org.hamcrest.CoreMatchers.equalTo
@@ -69,10 +81,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.lang.reflect.Modifier
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicReference
-import java.util.regex.Pattern
 
 private const val READ_CALENDAR = "android.permission.READ_CALENDAR"
 private const val BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT"
@@ -82,7 +90,6 @@
  */
 @RunWith(AndroidJUnit4::class)
 class AutoRevokeTest {
-
     private val context: Context = InstrumentationRegistry.getTargetContext()
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
@@ -94,8 +101,16 @@
     private lateinit var preMinVersionApkPath: String
     private lateinit var preMinVersionAppPackageName: String
 
+    @Rule
+    @JvmField
+    val storeExactTimeRule = DeviceConfigStateChangerRule(context,
+        DeviceConfig.NAMESPACE_PERMISSIONS, STORE_EXACT_TIME_KEY, "true")
+
     companion object {
         const val LOG_TAG = "AutoRevokeTest"
+        private const val STORE_EXACT_TIME_KEY = "permission_changes_store_exact_time"
+        private const val UNUSED_APPS_SOURCE_ID = "AndroidPermissionAutoRevoke"
+        private const val UNUSED_APPS_ISSUE_ID = "unused_apps_issue"
 
         @JvmStatic
         @BeforeClass
@@ -257,6 +272,61 @@
 
     @AppModeFull(reason = "Uses separate apps for testing")
     @Test
+    fun testAppWithPermissionsChangedRecently_doesNotGetPermissionRevoked() {
+        val unusedThreshold = 15_000L
+        withUnusedThresholdMs(unusedThreshold) {
+            withDummyApp {
+                // Setup
+                // Ensure app is considered unused and then change permission
+                Thread.sleep(unusedThreshold)
+                goToPermissions()
+                click("Calendar")
+                click("Allow")
+                goBack()
+                goBack()
+                goBack()
+
+                // Run
+                runAppHibernationJob(context, LOG_TAG)
+
+                // Verify that permission is not revoked because the permission was changed
+                // within the unused threshold even though the app itself is unused
+                assertPermission(PERMISSION_GRANTED)
+            }
+        }
+    }
+
+    @AppModeFull(reason = "Uses separate apps for testing")
+    @Test
+    fun testPermissionEventCleanupService_scrubsEvents() {
+        val unusedThreshold = 15_000L
+        withUnusedThresholdMs(unusedThreshold) {
+            withDummyApp {
+                // Setup
+                // Ensure app is considered unused
+                Thread.sleep(unusedThreshold)
+                goToPermissions()
+                click("Calendar")
+                click("Allow")
+                goBack()
+                goBack()
+                goBack()
+                // Run with threshold where events would be cleaned up
+                withUnusedThresholdMs(0) {
+                    runPermissionEventCleanupJob(context)
+                    Thread.sleep(3000L)
+                }
+
+                runAppHibernationJob(context, LOG_TAG)
+
+                // Verify that permission is revoked because there are no recent permission changes
+                assertPermission(PERMISSION_DENIED)
+            }
+        }
+    }
+
+    @AppModeFull(reason = "Uses separate apps for testing")
+    @Test
     fun testPreMinAutoRevokeVersionUnusedApp_doesntGetPermissionRevoked() {
         withUnusedThresholdMs(3L) {
             withDummyApp(preMinVersionApkPath, preMinVersionAppPackageName) {
@@ -376,6 +446,88 @@
         }
     }
 
+    @AppModeFull(reason = "Uses separate apps for testing")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    fun testAutoRevoke_showsUpInSafetyCenter() {
+        withSafetyCenterEnabled {
+            withUnusedThresholdMs(3L) {
+                withDummyApp {
+                    startAppAndAcceptPermission()
+
+                    killDummyApp()
+
+                    // Run
+                    runAppHibernationJob(context, LOG_TAG)
+
+                    // Verify
+                    val safetyCenterManager =
+                        context.getSystemService(SafetyCenterManager::class.java)!!
+                    eventually {
+                        val issues = ArrayList<SafetyCenterIssue>()
+                        runWithShellPermissionIdentity {
+                            val safetyCenterData = safetyCenterManager!!.safetyCenterData
+                            issues.addAll(safetyCenterData.issues)
+                        }
+                        val issueId = SafetyCenterIds.encodeToString(
+                                SafetyCenterIssueId.newBuilder()
+                                        .setSafetyCenterIssueKey(SafetyCenterIssueKey.newBuilder()
+                                                .setSafetySourceId(UNUSED_APPS_SOURCE_ID)
+                                                .setSafetySourceIssueId(UNUSED_APPS_ISSUE_ID)
+                                                .setUserId(UserHandle.myUserId())
+                                                .build())
+                                        .setIssueTypeId(UNUSED_APPS_ISSUE_ID)
+                                        .build())
+                        assertTrue(issues.any { it.id == issueId })
+                    }
+                }
+            }
+        }
+    }
+
+    @AppModeFull(reason = "Uses separate apps for testing")
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    fun testAutoRevoke_goToUnusedAppsPage_removesSafetyCenterIssue() {
+        withSafetyCenterEnabled {
+            withUnusedThresholdMs(3L) {
+                withDummyApp {
+                    startAppAndAcceptPermission()
+
+                    killDummyApp()
+
+                    // Run
+                    runAppHibernationJob(context, LOG_TAG)
+
+                    // Go to unused apps page
+                    openUnusedAppsNotification()
+                    waitFindObject(By.text(supportedAppPackageName))
+
+                    // Verify
+                    val safetyCenterManager =
+                        context.getSystemService(SafetyCenterManager::class.java)!!
+                    eventually {
+                        val issues = ArrayList<SafetyCenterIssue>()
+                        runWithShellPermissionIdentity {
+                            val safetyCenterData = safetyCenterManager!!.safetyCenterData
+                            issues.addAll(safetyCenterData.issues)
+                        }
+                        val issueId = SafetyCenterIds.encodeToString(
+                                SafetyCenterIssueId.newBuilder()
+                                        .setSafetyCenterIssueKey(SafetyCenterIssueKey.newBuilder()
+                                                .setSafetySourceId(UNUSED_APPS_SOURCE_ID)
+                                                .setSafetySourceIssueId(UNUSED_APPS_ISSUE_ID)
+                                                .setUserId(UserHandle.myUserId())
+                                                .build())
+                                        .setIssueTypeId(UNUSED_APPS_ISSUE_ID)
+                                        .build())
+                        assertFalse(issues.any { it.id == issueId })
+                    }
+                }
+            }
+        }
+    }
+
     private fun isAutomotiveDevice(): Boolean {
         return context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
     }
@@ -440,6 +592,7 @@
             waitFindObject(By.res("com.android.permissioncontroller:id/permission_allow_button"))
                     .click()
         }
+        waitForIdle()
     }
 
     private fun clickUninstallIcon() {
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index eae7276..5c861b5 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -47,6 +47,8 @@
         // which provides assertThrows
         "testng",
         "bluetooth-test-util-lib",
+        "CtsAccessibilityCommon",
+        "safety-center-internal-data",
     ],
     jni_libs: [
         "libctspermission_jni",
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index dc19893..8f3d39b 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -71,6 +71,15 @@
                 <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
+        <service android:name=".AccessibilityTestService"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+            </intent-filter>
+            <meta-data android:name="android.accessibilityservice"
+                android:resource="@xml/test_accessibilityservice"/>
+        </service>
     </application>
 
     <!--
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 58ee52f..56a72c9 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -91,6 +91,7 @@
         <option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp28.apk" />
         <option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Full.apk" />
         <option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Scoped.apk" />
+        <option name="push" value="CtsAppThatHasNotificationListener.apk->/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
@@ -102,6 +103,7 @@
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.userapp" />
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp" />
         <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.appthathasnotificationlistener" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission/AppThatHasNotificationListener/Android.bp b/tests/tests/permission/AppThatHasNotificationListener/Android.bp
new file mode 100644
index 0000000..419ab5d
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/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: "CtsAppThatHasNotificationListener",
+    defaults: [
+        "cts_defaults",
+        "mts-target-sdk-version-current",
+    ],
+    sdk_version: "current",
+    min_sdk_version: "30",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+        "mts-permission",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml b/tests/tests/permission/AppThatHasNotificationListener/AndroidManifest.xml
new file mode 100644
index 0000000..03d23df
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/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="android.permission.cts.appthathasnotificationlistener"
+          android:versionCode="1">
+
+    <application android:label="CtsNotificationListener">
+        <service
+            android:name=".CtsNotificationListenerService"
+            android:label="CtsNotificationListener"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
new file mode 100644
index 0000000..2bd423e
--- /dev/null
+++ b/tests/tests/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
@@ -0,0 +1,21 @@
+/*
+ * 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.appthathasnotificationlistener;
+
+import android.service.notification.NotificationListenerService;
+
+public class CtsNotificationListenerService extends NotificationListenerService {}
diff --git a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
index dfcc38e..8dd3e96 100644
--- a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
+++ b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
@@ -16,9 +16,16 @@
 
 package android.permission.cts;
 
+import android.app.UiAutomation;
+import android.os.Process;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assert;
 
 /** Common test utilities */
 public class TestUtils {
@@ -123,4 +130,42 @@
             }
         }
     }
+
+    /**
+     * Run the job and then wait for completion
+     */
+    public static void runJobAndWaitUntilCompleted(
+            String packageName,
+            int jobId, long timeout) {
+        runJobAndWaitUntilCompleted(packageName, jobId, timeout,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+    }
+
+    /**
+     * Run the job and then wait for completion
+     */
+    public static void runJobAndWaitUntilCompleted(
+            String packageName,
+            int jobId,
+            long timeout,
+            UiAutomation automation) {
+        String runJobCmd = "cmd jobscheduler run -u " + Process.myUserHandle().getIdentifier()
+                + " -f " + packageName + " " + jobId;
+        String statusCmd = "cmd jobscheduler get-job-state -u "
+                + Process.myUserHandle().getIdentifier() + " " + packageName + " " + jobId;
+
+        SystemUtil.runWithShellPermissionIdentity(automation, () -> {
+            SystemUtil.runShellCommand(automation, runJobCmd);
+            Thread.sleep(500);
+            try {
+                eventually(() -> Assert.assertEquals(
+                        "The job is probably still running",
+                        "waiting",
+                        SystemUtil.runShellCommand(automation, statusCmd).trim()),
+                        timeout);
+            } catch (Throwable e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
 }
diff --git a/tests/tests/permission/res/xml/test_accessibilityservice.xml b/tests/tests/permission/res/xml/test_accessibilityservice.xml
new file mode 100644
index 0000000..fa87e2e
--- /dev/null
+++ b/tests/tests/permission/res/xml/test_accessibilityservice.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:canRetrieveWindowContent="true"
+    android:accessibilityFlags="flagDefault"
+    android:notificationTimeout="0" />
diff --git a/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt b/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
new file mode 100644
index 0000000..513e1df
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
@@ -0,0 +1,339 @@
+/*
+ * 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 android.accessibility.cts.common.InstrumentedAccessibilityService
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Process
+import android.permission.cts.NotificationListenerUtils.assertEmptyNotification
+import android.permission.cts.NotificationListenerUtils.assertNotificationExist
+import android.permission.cts.NotificationListenerUtils.cancelNotification
+import android.permission.cts.NotificationListenerUtils.cancelNotifications
+import android.permission.cts.NotificationListenerUtils.getNotification
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueDoesNotExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterStarted
+import android.permission.cts.SafetyCenterUtils.deleteDeviceConfigPrivacyProperty
+import android.permission.cts.SafetyCenterUtils.deviceSupportsSafetyCenter
+import android.permission.cts.SafetyCenterUtils.setDeviceConfigPrivacyProperty
+import android.platform.test.annotations.AppModeFull
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.ProtoUtils
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.server.job.nano.JobSchedulerServiceDumpProto
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@AppModeFull(
+    reason = "Cannot set system settings as instant app. Also we never show an accessibility " +
+        "notification for instant apps."
+)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+class AccessibilityPrivacySourceTest {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context: Context = instrumentation.targetContext
+    private val mAccessibilityServiceRule =
+        InstrumentedAccessibilityServiceTestRule(AccessibilityTestService::class.java, false)
+    private val permissionControllerPackage = context.packageManager.permissionControllerPackageName
+    private val accessibilityTestService =
+        ComponentName(context, AccessibilityTestService::class.java).flattenToString()
+    private val safetyCenterIssueId = "accessibility_$accessibilityTestService"
+    private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)
+
+    @get:Rule
+    val deviceConfigSafetyCenterEnabled =
+        DeviceConfigStateChangerRule(
+            context, DeviceConfig.NAMESPACE_PRIVACY, SAFETY_CENTER_ENABLED, true.toString())
+
+    @get:Rule
+    val deviceConfigA11ySourceEnabled =
+        DeviceConfigStateChangerRule(
+            context, DeviceConfig.NAMESPACE_PRIVACY, ACCESSIBILITY_SOURCE_ENABLED, true.toString())
+
+    @get:Rule
+    val deviceConfigA11yListenerDisabled =
+        DeviceConfigStateChangerRule(
+            context,
+            DeviceConfig.NAMESPACE_PRIVACY,
+            ACCESSIBILITY_LISTENER_ENABLED,
+            false.toString())
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(deviceSupportsSafetyCenter(context))
+        InstrumentedAccessibilityService.disableAllServices()
+        runShellCommand("input keyevent KEYCODE_WAKEUP")
+        resetPermissionController()
+        cancelNotifications(permissionControllerPackage)
+    }
+
+    @After
+    fun cleanup() {
+        cancelNotifications(permissionControllerPackage)
+        runWithShellPermissionIdentity { safetyCenterManager?.clearAllSafetySourceDataForTests() }
+    }
+
+    @Test
+    fun testJobSendsNotification() {
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+    }
+
+    @Test
+    fun testJobSendsNotificationOnEnable() {
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
+        cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+        InstrumentedAccessibilityService.disableAllServices()
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, false.toString())
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS, "0")
+
+        // enable service again and verify a notification
+        try {
+            mAccessibilityServiceRule.enableService()
+            runJobAndWaitUntilCompleted()
+            assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+        } finally {
+            deleteDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS)
+        }
+    }
+
+    @Test
+    fun testJobSendsIssuesToSafetyCenter() {
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertSafetyCenterIssueExist(
+            SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+    }
+
+    @Test
+    fun testJobDoesNotSendNotificationInSecondRunForSameService() {
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+        cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+        runJobAndWaitUntilCompleted()
+        assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+    }
+
+    @Test
+    fun testAccessibilityListenerSendsIssueToSafetyCenter() {
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
+        val automation = getAutomation()
+        mAccessibilityServiceRule.enableService()
+        TestUtils.eventually(
+            {
+                assertSafetyCenterIssueExist(
+                    SC_ACCESSIBILITY_SOURCE_ID,
+                    safetyCenterIssueId,
+                    SC_ACCESSIBILITY_ISSUE_TYPE_ID,
+                    automation)
+            },
+            TIMEOUT_MILLIS)
+        automation.destroy()
+    }
+
+    @Test
+    fun testJobWithDisabledServiceDoesNotSendNotification() {
+        runJobAndWaitUntilCompleted()
+        assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+    }
+
+    @Test
+    fun testJobWithDisabledServiceDoesNotSendIssueToSafetyCenter() {
+        runJobAndWaitUntilCompleted()
+        assertSafetyCenterIssueDoesNotExist(
+            SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+    }
+
+    @Test
+    fun testJobWithAccessibilityFeatureDisabledDoesNotSendNotification() {
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+    }
+
+    @Test
+    fun testJobWithAccessibilityFeatureDisabledDoesNotSendIssueToSafetyCenter() {
+        setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertSafetyCenterIssueDoesNotExist(
+            SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+    }
+
+    @Test
+    fun testJobWithSafetyCenterDisabledDoesNotSendNotification() {
+        setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+    }
+
+    @Test
+    fun testJobWithSafetyCenterDisabledDoesNotSendIssueToSafetyCenter() {
+        setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        assertSafetyCenterIssueDoesNotExist(
+            SC_ACCESSIBILITY_SOURCE_ID, safetyCenterIssueId, SC_ACCESSIBILITY_ISSUE_TYPE_ID)
+    }
+
+    @Test
+    fun testNotificationClickOpenSafetyCenter() {
+        mAccessibilityServiceRule.enableService()
+        runJobAndWaitUntilCompleted()
+        val statusBarNotification =
+            getNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+        Assert.assertNotNull(statusBarNotification)
+        val contentIntent = statusBarNotification!!.notification.contentIntent
+        contentIntent.send()
+        assertSafetyCenterStarted()
+    }
+
+    private fun getAutomation(): UiAutomation {
+        return instrumentation.getUiAutomation(
+            UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
+    }
+
+    private fun runJobAndWaitUntilCompleted() {
+        TestUtils.runJobAndWaitUntilCompleted(
+            permissionControllerPackage, ACCESSIBILITY_JOB_ID, TIMEOUT_MILLIS, getAutomation())
+    }
+
+    /** Reset the permission controllers state. */
+    @Throws(Throwable::class)
+    private fun resetPermissionController() {
+        PermissionUtils.clearAppState(permissionControllerPackage)
+        val currentUserId = Process.myUserHandle().identifier
+
+        // Wait until jobs are cleared
+        TestUtils.eventually(
+            {
+                val dump = getJobSchedulerDump()
+                for (job in dump!!.registeredJobs) {
+                    if (job.dump.sourceUserId == currentUserId &&
+                        job.dump.sourcePackageName == permissionControllerPackage) {
+                        Assert.assertFalse(
+                            job.dump.jobInfo.service.className.contains("AccessibilityJobService"))
+                    }
+                }
+            },
+            TIMEOUT_MILLIS)
+
+        runShellCommand(
+            "cmd jobscheduler reset-execution-quota -u " +
+                "${Process.myUserHandle().identifier} $permissionControllerPackage")
+
+        // Setup up permission controller again (simulate a reboot)
+        val permissionControllerSetupIntent =
+            Intent(ACTION_SET_UP_ACCESSIBILITY_CHECK).apply {
+                setPackage(permissionControllerPackage)
+                setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+            }
+
+        // Query for the setup broadcast receiver
+        val resolveInfos =
+            context.packageManager.queryBroadcastReceivers(permissionControllerSetupIntent, 0)
+
+        if (resolveInfos.size > 0) {
+            context.sendBroadcast(permissionControllerSetupIntent)
+        } else {
+            context.sendBroadcast(
+                Intent().apply {
+                    setClassName(permissionControllerPackage, AccessibilityOnBootReceiver)
+                    setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                    setPackage(permissionControllerPackage)
+                })
+        }
+
+        // Wait until jobs are set up
+        TestUtils.eventually(
+            {
+                val dump = getJobSchedulerDump()
+                for (job in dump!!.registeredJobs) {
+                    if (job.dump.sourceUserId == currentUserId &&
+                        job.dump.sourcePackageName == permissionControllerPackage &&
+                        job.dump.jobInfo.service.className.contains("AccessibilityJobService")) {
+                        return@eventually
+                    }
+                }
+                Assert.fail("accessibility job not found")
+            },
+            TIMEOUT_MILLIS)
+    }
+
+    @Throws(Exception::class)
+    private fun getJobSchedulerDump(): JobSchedulerServiceDumpProto? {
+        return ProtoUtils.getProto(
+            getAutomation(),
+            JobSchedulerServiceDumpProto::class.java,
+            ProtoUtils.DUMPSYS_JOB_SCHEDULER)
+    }
+
+    companion object {
+        private const val SC_ACCESSIBILITY_SOURCE_ID = "AndroidAccessibility"
+        private const val ACCESSIBILITY_SOURCE_ENABLED = "sc_accessibility_source_enabled"
+        private const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+        private const val ACCESSIBILITY_LISTENER_ENABLED = "sc_accessibility_listener_enabled"
+        private const val ACCESSIBILITY_JOB_INTERVAL_MILLIS = "sc_accessibility_job_interval_millis"
+
+        private const val ACCESSIBILITY_JOB_ID = 6
+        private const val ACCESSIBILITY_NOTIFICATION_ID = 4
+        private const val TIMEOUT_MILLIS: Long = 10000
+
+        private const val SC_ACCESSIBILITY_ISSUE_TYPE_ID = "accessibility_privacy_issue"
+
+        private const val AccessibilityOnBootReceiver =
+            "com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
+        private const val ACTION_SET_UP_ACCESSIBILITY_CHECK =
+            "com.android.permissioncontroller.action.SET_UP_ACCESSIBILITY_CHECK"
+
+        @get:ClassRule
+        @JvmStatic
+        val ctsNotificationListenerHelper =
+            CtsNotificationListenerHelperRule(
+                InstrumentationRegistry.getInstrumentation().targetContext)
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt b/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt
new file mode 100644
index 0000000..9f5e3f1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/AccessibilityTestService.kt
@@ -0,0 +1,24 @@
+/*
+ * 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 android.accessibility.cts.common.InstrumentedAccessibilityService
+
+/**
+ * Test Accessibility Service
+ */
+class AccessibilityTestService : InstrumentedAccessibilityService()
diff --git a/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java b/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
new file mode 100644
index 0000000..f88b7ec
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
@@ -0,0 +1,449 @@
+/*
+ * 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 android.Manifest.permission.WRITE_DEVICE_CONFIG;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.os.Process.myUserHandle;
+import static android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.TestUtils.eventually;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.server.job.nano.JobPackageHistoryProto.START_PERIODIC_JOB;
+import static com.android.server.job.nano.JobPackageHistoryProto.STOP_PERIODIC_JOB;
+
+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 static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import static java.lang.Math.max;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.app.NotificationManager;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.nano.JobPackageHistoryProto;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Base test class used for {@code NotificationListenerCheckTest} and
+ * {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+        + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class BaseNotificationListenerCheckTest {
+    private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    protected static final String TEST_APP_PKG =
+            "android.permission.cts.appthathasnotificationlistener";
+    private static final String TEST_APP_NOTIFICATION_SERVICE =
+            TEST_APP_PKG + ".CtsNotificationListenerService";
+    protected static final String TEST_APP_NOTIFICATION_LISTENER_APK =
+            "/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk";
+
+    private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
+
+    /**
+     * Device config property for whether notification listener check is enabled on the device
+     */
+    private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
+            "notification_listener_check_enabled";
+
+    /**
+     * Device config property for time period in milliseconds after which current enabled
+     * notification
+     * listeners are queried
+     */
+    private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+            "notification_listener_check_interval_millis";
+
+    protected static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+            SECONDS.toMillis(1);
+
+    private static final String PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW =
+            "qc_max_job_count_per_rate_limiting_window";
+
+    private static final String PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS =
+            "qc_rate_limiting_window_ms";
+
+    private static final String ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK =
+            "com.android.permissioncontroller.action.SET_UP_NOTIFICATION_LISTENER_CHECK";
+    private static final String NotificationListenerOnBootReceiver =
+            "com.android.permissioncontroller.privacysources.SetupPeriodicNotificationListenerCheck";
+
+    /**
+     * ID for notification shown by
+     * {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}.
+     */
+    public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
+
+    protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
+    protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000;
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final PackageManager sPackageManager = sContext.getPackageManager();
+    private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+            .getUiAutomation();
+
+    private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+            .getPermissionControllerPackageName();
+
+    private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
+
+    // Override SafetyCenter enabled flag
+    @Rule
+    public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+                    Boolean.toString(true));
+
+    // Override NlsCheck enabled flag
+    @Rule
+    public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+                    Boolean.toString(true));
+
+    // Override general notification interval from once every day to once ever 1 second
+    @Rule
+    public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
+                    Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
+
+    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+    @Rule
+    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig1 =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW,
+                    Integer.toString(3000000));
+
+    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+    @Rule
+    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig2 =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS,
+                    Integer.toString(30000));
+
+    @Rule
+    public CtsNotificationListenerHelperRule ctsNotificationListenerHelper =
+            new CtsNotificationListenerHelperRule(sContext);
+
+    @BeforeClass
+    public static void beforeClassSetup() throws Exception {
+        // Disallow any OEM enabled NLS
+        disallowPreexistingNotificationListeners();
+    }
+
+    @AfterClass
+    public static void afterClassTearDown() throws Throwable {
+        // Reallow any previously OEM allowed NLS
+        reallowPreexistingNotificationListeners();
+    }
+
+    private static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
+        runWithShellPermissionIdentity(() -> {
+            boolean valueWasSet =  DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    /* name = */ propertyName,
+                    /* value = */ value,
+                    /* makeDefault = */ false);
+            if (!valueWasSet) {
+                throw new  IllegalStateException("Could not set " + propertyName + " to " + value);
+            }
+        }, WRITE_DEVICE_CONFIG);
+    }
+
+    /**
+     * Enable or disable notification listener check
+     */
+    protected static void setNotificationListenerCheckEnabled(boolean enabled) {
+        setDeviceConfigPrivacyProperty(
+                PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+                /* value = */ String.valueOf(enabled));
+    }
+
+    /**
+     * Allow or disallow a {@link NotificationListenerService} component for the current user
+     *
+     * @param listenerComponent {@link NotificationListenerService} component to allow or disallow
+     */
+    private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
+            boolean allowed) {
+        String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
+                + listenerComponent.flattenToString();
+        runShellCommand(command);
+    }
+
+    private static void disallowPreexistingNotificationListeners() {
+        runWithShellPermissionIdentity(() -> {
+            NotificationManager notificationManager =
+                    sContext.getSystemService(NotificationManager.class);
+            sPreviouslyEnabledNotificationListeners =
+                    notificationManager.getEnabledNotificationListeners();
+        });
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
+                    + " previously allowed notification listeners. Disabling before test run.");
+        }
+        for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+            setNotificationListenerServiceAllowed(listener, false);
+        }
+    }
+
+    private static void reallowPreexistingNotificationListeners() {
+        if (DEBUG) {
+            Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
+                    + " previously allowed notification listeners found before test run.");
+        }
+        for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+            setNotificationListenerServiceAllowed(listener, true);
+        }
+    }
+
+    protected void allowTestAppNotificationListenerService() {
+        setNotificationListenerServiceAllowed(
+                new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
+    }
+
+    protected void disallowTestAppNotificationListenerService() {
+        setNotificationListenerServiceAllowed(
+                new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
+    }
+
+    /**
+     * Get the state of the job scheduler
+     */
+    private static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
+        return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
+                ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+    }
+
+    /**
+     * Get the last time the NOTIFICATION_LISTENER_CHECK_JOB_ID job was started/stopped for
+     * permission
+     * controller.
+     *
+     * @param event the job event (start/stop)
+     * @return the last time the event happened.
+     */
+    private static long getLastJobTime(int event) throws Exception {
+        int permControllerUid = sPackageManager.getPackageUid(PERMISSION_CONTROLLER_PKG, 0);
+
+        long lastTime = -1;
+
+        for (JobPackageHistoryProto.HistoryEvent historyEvent :
+                getJobSchedulerDump().history.historyEvent) {
+            if (historyEvent.uid == permControllerUid
+                    && historyEvent.jobId == NOTIFICATION_LISTENER_CHECK_JOB_ID
+                    && historyEvent.event == event) {
+                lastTime = max(lastTime,
+                        System.currentTimeMillis() - historyEvent.timeSinceEventMs);
+            }
+        }
+
+        return lastTime;
+    }
+
+    /**
+     * Force a run of the notification listener check.
+     */
+    protected static void runNotificationListenerCheck() throws Throwable {
+        // Sleep a little to make sure we don't have overlap in timing
+        Thread.sleep(1000);
+
+        long beforeJob = System.currentTimeMillis();
+
+        // Sleep a little to avoid raciness in time keeping
+        Thread.sleep(1000);
+
+        runShellCommand("cmd jobscheduler run -u " + myUserHandle().getIdentifier() + " -f "
+                + PERMISSION_CONTROLLER_PKG + " " + NOTIFICATION_LISTENER_CHECK_JOB_ID);
+
+        eventually(() -> {
+            long startTime = getLastJobTime(START_PERIODIC_JOB);
+            assertTrue(startTime + " !> " + beforeJob, startTime > beforeJob);
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+
+        // We can't simply require startTime <= endTime because the time being reported isn't
+        // accurate, and sometimes the end time may come before the start time by around 100 ms.
+        eventually(() -> {
+            long stopTime = getLastJobTime(STOP_PERIODIC_JOB);
+            assertTrue(stopTime + " !> " + beforeJob, stopTime > beforeJob);
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Skip tests for if Safety Center not supported
+     */
+    protected void assumeDeviceSupportsSafetyCenter() {
+        assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+    }
+
+    /**
+     * Skip tests for if Safety Center IS supported
+     */
+    protected void assumeDeviceDoesNotSupportSafetyCenter() {
+        assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+    }
+
+    protected void wakeUpAndDismissKeyguard() {
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+        runShellCommand("wm dismiss-keyguard");
+    }
+
+    /**
+     * Reset the permission controllers state before each test
+     */
+    protected void resetPermissionControllerBeforeEachTest() throws Throwable {
+        resetPermissionController();
+
+        // ensure no posted notification listener notifications exits
+        eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Reset job scheduler stats (to allow more jobs to be run)
+        runShellCommand(
+                "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+                        + PERMISSION_CONTROLLER_PKG);
+    }
+
+    /**
+     * Reset the permission controllers state.
+     */
+    private static void resetPermissionController() throws Throwable {
+        clearAppState(PERMISSION_CONTROLLER_PKG);
+        int currentUserId = myUserHandle().getIdentifier();
+
+        // Wait until jobs are cleared
+        eventually(() -> {
+            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+
+            for (RegisteredJob job : dump.registeredJobs) {
+                if (job.dump.sourceUserId == currentUserId) {
+                    assertNotEquals(job.dump.sourcePackageName, PERMISSION_CONTROLLER_PKG);
+                }
+            }
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Setup up permission controller again (simulate a reboot)
+        Intent permissionControllerSetupIntent = new Intent(
+                ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK).setPackage(
+                PERMISSION_CONTROLLER_PKG).setFlags(FLAG_RECEIVER_FOREGROUND);
+
+        // Query for the setup broadcast receiver
+        List<ResolveInfo> resolveInfos = sContext.getPackageManager().queryBroadcastReceivers(
+                permissionControllerSetupIntent, 0);
+
+        if (resolveInfos.size() > 0) {
+            sContext.sendBroadcast(permissionControllerSetupIntent);
+        } else {
+            sContext.sendBroadcast(new Intent()
+                    .setClassName(PERMISSION_CONTROLLER_PKG, NotificationListenerOnBootReceiver)
+                    .setFlags(FLAG_RECEIVER_FOREGROUND)
+                    .setPackage(PERMISSION_CONTROLLER_PKG));
+        }
+
+        // Wait until jobs are set up
+        eventually(() -> {
+            JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+
+            for (RegisteredJob job : dump.registeredJobs) {
+                if (job.dump.sourceUserId == currentUserId
+                        && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)
+                        && job.dump.jobInfo.service.className.contains(
+                        "NotificationListenerCheck")) {
+                    return;
+                }
+            }
+
+            fail("Permission controller jobs not found");
+        }, UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Preshow/dismiss cts NotificationListener notification as it negatively affects test results
+     * (can result in unexpected test pass/failures)
+     */
+    protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable {
+        // CtsNotificationListenerService isn't enabled at this point, but NotificationListener
+        // should be. Mark as notified by showing and dismissing
+        runNotificationListenerCheck();
+
+        // Ensure notification shows and dismiss
+        eventually(() -> assertNotNull(getNotification(true)),
+                UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Get a notification listener notification that is currently visible.
+     *
+     * @param cancelNotification if `true` the notification is canceled inside this method
+     * @return The notification or `null` if there is none
+     */
+    protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+        return NotificationUtils.getNotificationForPackageAndId(
+                PERMISSION_CONTROLLER_PKG,
+                NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
+                cancelNotification);
+    }
+
+    /**
+     * Clear any notifications related to NotificationListenerCheck to ensure clean test setup
+     */
+    protected void clearNotifications() throws Throwable {
+        // Clear notification if present
+        NotificationUtils.clearNotificationsForPackage(PERMISSION_CONTROLLER_PKG);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt b/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
new file mode 100644
index 0000000..b7fa960
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
@@ -0,0 +1,58 @@
+/*
+ * 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 android.content.ComponentName
+import android.content.Context
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule that enables and disables the CTS NotificationListenerService
+ */
+class CtsNotificationListenerHelperRule(context: Context) : TestRule {
+
+    private val notificationListenerComponentName = ComponentName(
+        context,
+        NotificationListener::class.java
+    )
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                try {
+                    // Allow NLS used to verify notifications sent
+                    SystemUtil.runShellCommand(ALLOW_NLS_COMMAND +
+                        notificationListenerComponentName.flattenToString())
+
+                    base.evaluate()
+                } finally {
+                    // Disallow NLS used to verify notifications sent
+                    SystemUtil.runShellCommand(DISALLOW_NLS_COMMAND +
+                        notificationListenerComponentName.flattenToString())
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val ALLOW_NLS_COMMAND = "cmd notification allow_listener "
+        private const val DISALLOW_NLS_COMMAND = "cmd notification disallow_listener "
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index e13f58b..ceb4ede 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
 import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
 import static android.content.Context.BIND_AUTO_CREATE;
@@ -47,6 +48,7 @@
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -59,6 +61,7 @@
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
@@ -75,10 +78,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ProtoUtils;
 import com.android.compatibility.common.util.mainline.MainlineModule;
 import com.android.compatibility.common.util.mainline.ModuleDetector;
@@ -120,13 +123,20 @@
     private static final int LOCATION_ACCESS_CHECK_JOB_ID = 0;
     private static final int LOCATION_ACCESS_CHECK_NOTIFICATION_ID = 0;
 
-    /** Whether to show location access check notifications. */
+    /**
+     * Whether to show location access check notifications.
+     */
     private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
             "location_access_check_enabled";
     private static final String PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS =
             "location_access_check_delay_millis";
     private static final String PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS =
             "location_access_check_periodic_interval_millis";
+    private static final String PROPERTY_BG_LOCATION_CHECK_ENABLED = "bg_location_check_is_enabled";
+    private static final String PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW =
+            "qc_max_job_count_per_rate_limiting_window";
+    private static final String PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS =
+            "qc_rate_limiting_window_ms";
 
     private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
     private static final long EXPECTED_TIMEOUT_MILLIS = 15000;
@@ -147,6 +157,45 @@
             "com.android.permissioncontroller.permission.service"
                     + ".LocationAccessCheck$SetupPeriodicBackgroundLocationAccessCheck";
 
+
+    /**
+     * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over
+     * again.
+     */
+    private static Boolean sCanAccessFineLocation = null;
+
+    private static ServiceConnection sConnection;
+    private static IAccessLocationOnCommand sLocationAccessor;
+
+    private static void assumeNotPlayManaged() throws Exception {
+        assumeFalse(ModuleDetector.moduleIsPlayManaged(
+                sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
+    }
+
+    // Override location access check flag
+    @Rule
+    public DeviceConfigStateChangerRule mPrivacyDeviceConfig =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+                    Boolean.toString(true));
+
+    // Override SafetyCenter enabled flag
+    @Rule
+    public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+                    Boolean.toString(true));
+
+    // Override BG location enabled flag
+    @Rule
+    public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgLocationCheckEnabled =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_BG_LOCATION_CHECK_ENABLED,
+                    Boolean.toString(true));
+
     // Override general notification interval
     @Rule
     public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckIntervalMillis =
@@ -163,23 +212,46 @@
                     PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS,
                     "50");
 
+    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+    @Rule
+    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig1 =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    PROPERTY_JOB_SCHEDULER_MAX_JOB_PER_RATE_LIMIT_WINDOW,
+                    Integer.toString(3000000));
+
+    // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+    @Rule
+    public DeviceConfigStateChangerRule sJobSchedulerDeviceConfig2 =
+            new DeviceConfigStateChangerRule(sContext,
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    PROPERTY_JOB_SCHEDULER_RATE_LIMIT_WINDOW_MILLIS,
+                    Integer.toString(30000));
+
     /**
-     * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over
-     * again.
+     * Change settings so that permission controller can show location access notifications more
+     * often.
      */
-    private static Boolean sCanAccessFineLocation = null;
+    @BeforeClass
+    public static void reduceDelays() {
+        runWithShellPermissionIdentity(() -> {
+            ContentResolver cr = sContext.getContentResolver();
+            // New settings will be applied in when permission controller is reset
+            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
+            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
+        });
+    }
 
-    private static ServiceConnection sConnection;
-    private static IAccessLocationOnCommand sLocationAccessor;
-
-    private DeviceConfigStateHelper mPrivacyDeviceConfig =
-            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_PRIVACY);
-    private static DeviceConfigStateHelper sJobSchedulerDeviceConfig =
-            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
-
-    private static void assumeNotPlayManaged() throws Exception {
-        assumeFalse(ModuleDetector.moduleIsPlayManaged(
-                sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
+    /**
+     * Reset settings so that permission controller runs normally.
+     */
+    @AfterClass
+    public static void resetDelays() throws Throwable {
+        runWithShellPermissionIdentity(() -> {
+            ContentResolver cr = sContext.getContentResolver();
+            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
+            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
+        });
     }
 
     /**
@@ -297,7 +369,6 @@
      * controller.
      *
      * @param event the job event (start/stop)
-     *
      * @return the last time the event happened.
      */
     private static long getLastJobTime(int event) throws Exception {
@@ -424,25 +495,6 @@
                 NotificationListener.class).flattenToString()));
     }
 
-    /**
-     * Change settings so that permission controller can show location access notifications more
-     * often.
-     */
-    @BeforeClass
-    public static void reduceDelays() {
-        runWithShellPermissionIdentity(() -> {
-            ContentResolver cr = sContext.getContentResolver();
-
-            // New settings will be applied in when permission controller is reset
-            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
-            Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
-
-            // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
-            sJobSchedulerDeviceConfig.set("qc_max_job_count_per_rate_limiting_window", "3000000");
-            sJobSchedulerDeviceConfig.set("qc_rate_limiting_window_ms", "30000");
-        });
-    }
-
     @BeforeClass
     public static void installBackgroundAccessApp() throws Exception {
         installBackgroundAccessApp(false);
@@ -473,6 +525,21 @@
         sLocationAccessor = null;
     }
 
+    private void setDeviceConfigProperty(
+            @NonNull String propertyName,
+            @NonNull String value) {
+        runWithShellPermissionIdentity(() -> {
+            boolean valueWasSet = DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    propertyName,
+                    value,
+                    false);
+            if (!valueWasSet) {
+                throw new IllegalStateException("Could not set " + propertyName + " to " + value);
+            }
+        }, WRITE_DEVICE_CONFIG);
+    }
+
 
     private static void installForegroundAccessApp() throws Exception {
         unbindService();
@@ -543,8 +610,8 @@
      * Enable location access check
      */
     public void enableLocationAccessCheck() throws Throwable {
-        mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true");
-
+        setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+                "true");
         // Run a location access check to update enabled state inside permission controller
         runLocationCheck();
     }
@@ -553,8 +620,8 @@
      * Disable location access check
      */
     private void disableLocationAccessCheck() throws Throwable {
-        mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false");
-
+        setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+                "false");
         // Run a location access check to update enabled state inside permission controller
         runLocationCheck();
     }
@@ -669,27 +736,10 @@
     }
 
     /**
-     * Reset settings so that permission controller runs normally.
-     */
-    @AfterClass
-    public static void resetDelays() throws Throwable {
-        runWithShellPermissionIdentity(() -> {
-            ContentResolver cr = sContext.getContentResolver();
-
-            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
-            Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
-
-            sJobSchedulerDeviceConfig.restoreOriginalValues();
-        });
-    }
-
-    /**
      * Reset location access check
      */
     @After
     public void resetPrivacyConfig() throws Throwable {
-        mPrivacyDeviceConfig.restoreOriginalValues();
-
         // Run a location access check to update enabled state inside permission controller
         runLocationCheck();
     }
@@ -703,7 +753,6 @@
     public void notificationIsShown() throws Throwable {
         accessLocation();
         runLocationCheck();
-
         eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
     }
 
@@ -882,4 +931,23 @@
         runLocationCheck();
         assertNull(getNotification(false));
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void notificationOnClickOpensSafetyCenter() throws Throwable {
+        accessLocation();
+        runLocationCheck();
+
+        StatusBarNotification currentNotification = eventually(() -> {
+            StatusBarNotification notification = getNotification(false);
+            assertNotNull(notification);
+            return notification;
+        }, EXPECTED_TIMEOUT_MILLIS);
+
+        // Verify content intent
+        PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+        contentIntent.send();
+
+        SafetyCenterUtils.assertSafetyCenterStarted();
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
new file mode 100644
index 0000000..88006b9
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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 android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+import static android.permission.cts.TestUtils.eventually;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.PendingIntent;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+        + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class NotificationListenerCheckTest extends BaseNotificationListenerCheckTest {
+
+    @Before
+    public void setup() throws Throwable {
+        // Skip tests if safety center not allowed
+        assumeDeviceSupportsSafetyCenter();
+
+        wakeUpAndDismissKeyguard();
+        resetPermissionControllerBeforeEachTest();
+
+        // Cts NLS is required to verify sent Notifications, however, we don't want it to show up in
+        // testing
+        triggerAndDismissCtsNotificationListenerNotification();
+
+        clearNotifications();
+
+        // Install and allow the app with NLS for testing
+        install(TEST_APP_NOTIFICATION_LISTENER_APK);
+        allowTestAppNotificationListenerService();
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        // Disallow and uninstall the app with NLS for testing
+        disallowTestAppNotificationListenerService();
+        uninstallApp(TEST_APP_PKG);
+
+        clearNotifications();
+    }
+
+    @Test
+    public void noNotificationIfFeatureDisabled() throws Throwable {
+        setNotificationListenerCheckEnabled(false);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void noNotificationIfSafetyCenterDisabled() throws Throwable {
+        SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsShown() throws Throwable {
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull("Expected notification, none found", getNotification(false)),
+                UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsShownOnlyOnce() throws Throwable {
+        runNotificationListenerCheck();
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsShownAgainAfterClear() throws Throwable {
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        clearAppState(TEST_APP_PKG);
+
+        // Wait until package is cleared and permission controller has cleared the state
+        Thread.sleep(2000);
+
+        allowTestAppNotificationListenerService();
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        uninstallApp(TEST_APP_PKG);
+
+        // Wait until package permission controller has cleared the state
+        Thread.sleep(2000);
+
+        install(TEST_APP_NOTIFICATION_LISTENER_APK);
+
+        allowTestAppNotificationListenerService();
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsShownAgainAfterDisableAndReenableAppNotificationListener()
+            throws Throwable {
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Disallow NLS, and run NLS check job. This  should clear NLS off notified list
+        disallowTestAppNotificationListenerService();
+        runNotificationListenerCheck();
+
+        // Re-allow NLS, and run NLS check job. This work now that it's cleared NLS off notified
+        // list
+        allowTestAppNotificationListenerService();
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void removeNotificationOnUninstall() throws Throwable {
+        runNotificationListenerCheck();
+
+        eventually(() -> assertNotNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        uninstallApp(TEST_APP_PKG);
+
+        // Wait until package permission controller has cleared the state
+        Thread.sleep(2000);
+
+        eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationIsNotShownAfterDisableAppNotificationListener() throws Throwable {
+        disallowTestAppNotificationListenerService();
+
+        runNotificationListenerCheck();
+
+        // We don't expect a notification, but try to trigger one anyway
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void notificationOnClick_opensSafetyCenter() throws Throwable {
+        runNotificationListenerCheck();
+
+        StatusBarNotification currentNotification = eventually(
+                () -> {
+                    StatusBarNotification notification = getNotification(false);
+                    assertNotNull(notification);
+                    return notification;
+                }, UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Verify content intent
+        PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+        contentIntent.send();
+
+        SafetyCenterUtils.assertSafetyCenterStarted();
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
new file mode 100644
index 0000000..a346de6
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+
+import static org.junit.Assert.assertNull;
+
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+        + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class NotificationListenerCheckWithSafetyCenterUnsupportedTest
+        extends BaseNotificationListenerCheckTest  {
+
+    @Before
+    public void setup() throws Throwable {
+        // Skip tests if safety center is supported
+        assumeDeviceDoesNotSupportSafetyCenter();
+
+        wakeUpAndDismissKeyguard();
+        resetPermissionControllerBeforeEachTest();
+
+        clearNotifications();
+
+        // Install and allow the app with NLS for testing
+        install(TEST_APP_NOTIFICATION_LISTENER_APK);
+        allowTestAppNotificationListenerService();
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        // Disallow and uninstall the app with NLS for testing
+        disallowTestAppNotificationListenerService();
+        uninstallApp(TEST_APP_PKG);
+
+        clearNotifications();
+    }
+
+    @Test
+    public void noNotifications_featureEnabled_safetyCenterEnabled() throws Throwable {
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void noNotifications_featureDisabled_safetyCenterEnabled() throws Throwable {
+        setNotificationListenerCheckEnabled(false);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void noNotifications_featureEnabled_safetyCenterDisabled() throws Throwable {
+        SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void noNotifications_featureDisabled_safetyCenterDisabled() throws Throwable {
+        setNotificationListenerCheckEnabled(false);
+        SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+        runNotificationListenerCheck();
+
+        ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+                ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt b/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt
new file mode 100644
index 0000000..0ee8a3d
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationListenerUtils.kt
@@ -0,0 +1,89 @@
+/*
+ * 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 android.service.notification.StatusBarNotification
+import org.junit.Assert
+import android.permission.cts.TestUtils.ensure
+import android.permission.cts.TestUtils.eventually
+
+object NotificationListenerUtils {
+
+    private const val NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS = 5000L
+    private const val NOTIFICATION_WAIT_MILLIS = 2000L
+    private val notificationService = NotificationListener.getInstance()
+
+    @JvmStatic
+    fun assertEmptyNotification(packageName: String, notificationId: Int) {
+        ensure({
+            Assert.assertNull(
+            "Expected no notification",
+            getNotification(packageName, notificationId))
+        }, NOTIFICATION_WAIT_MILLIS)
+    }
+
+    @JvmStatic
+    fun assertNotificationExist(packageName: String, notificationId: Int) {
+        eventually({
+            Assert.assertNotNull(
+                "Expected notification, none found",
+                getNotification(packageName, notificationId))
+        }, NOTIFICATION_WAIT_MILLIS)
+    }
+
+    @JvmStatic
+    fun cancelNotification(packageName: String, notificationId: Int) {
+        val notification = getNotification(packageName, notificationId)
+        if (notification != null) {
+            notificationService.cancelNotification(notification.key)
+            eventually({
+                Assert.assertTrue(getNotification(packageName, notificationId) == null)
+            }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+        }
+    }
+
+    @JvmStatic
+    fun cancelNotifications(packageName: String) {
+        val notifications = getNotifications(packageName)
+        if (notifications.isNotEmpty()) {
+            notifications.forEach { notification ->
+                notificationService.cancelNotification(notification.key)
+            }
+            eventually({
+                Assert.assertTrue(getNotifications(packageName).isEmpty())
+            }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+        }
+    }
+
+    @JvmStatic
+    fun getNotification(packageName: String, notificationId: Int): StatusBarNotification? {
+        return getNotifications(packageName).firstOrNull {
+            it.id == notificationId
+        }
+    }
+
+    @JvmStatic
+    fun getNotifications(packageName: String): List<StatusBarNotification> {
+        val notifications: MutableList<StatusBarNotification> = ArrayList()
+        for (notification in notificationService.activeNotifications) {
+            if (notification.packageName == packageName) {
+                notifications.add(notification)
+            }
+        }
+        return notifications
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt b/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt
new file mode 100644
index 0000000..7c50837
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NotificationUtils.kt
@@ -0,0 +1,120 @@
+/*
+ * 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 android.service.notification.StatusBarNotification
+import org.junit.Assert
+import java.util.concurrent.TimeUnit
+
+/**
+ * Utility methods to interact with NotificationManager through the CTS NotificationListenerService
+ * to get or clear notifications.
+ */
+object NotificationUtils {
+
+    private val NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(5)
+
+    /**
+     * Get a notification listener notification that is currently visible.
+     *
+     * @param cancelNotification if `true` the notification is canceled inside this method
+     * @return The notification or `null` if there is none
+     */
+    @JvmStatic
+    @Throws(Throwable::class)
+    fun getNotificationForPackageAndId(
+        pkg: String,
+        id: Int,
+        cancelNotification: Boolean
+    ): StatusBarNotification? {
+        val notificationService = NotificationListener.getInstance()
+        val notifications: List<StatusBarNotification> = getNotificationsForPackage(pkg)
+        if (notifications.isEmpty()) {
+            return null
+        }
+        for (notification in notifications) {
+            if (notification.id == id) {
+                if (cancelNotification) {
+                    clearNotification(notification)
+                }
+                return notification
+            }
+        }
+        return null
+    }
+
+    /**
+     * Clears all currently visible notifications for a specified package.
+     */
+    @JvmStatic
+    @Throws(Throwable::class)
+    fun clearNotificationsForPackage(pkg: String) {
+        val notifications: List<StatusBarNotification> = getNotificationsForPackage(pkg)
+        if (notifications.isEmpty()) {
+            return
+        }
+
+        clearNotifications(notifications)
+    }
+
+    /** Clears the specified notification and ensures (asserts) it was removed */
+    @JvmStatic
+    @Throws(Throwable::class)
+    fun clearNotification(notification: StatusBarNotification) {
+        val notificationService = NotificationListener.getInstance()
+        notificationService.cancelNotification(notification.key)
+
+        // Wait for notification to get canceled
+        TestUtils.eventually({
+            Assert.assertFalse(
+                listOf(*notificationService.activeNotifications)
+                    .contains(notification)
+            )
+        }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+    }
+
+    private fun clearNotifications(notifications: List<StatusBarNotification>) {
+        val notificationService = NotificationListener.getInstance()
+        notifications.forEach { notificationService.cancelNotification(it.key) }
+
+        // Wait for notification to get canceled
+        TestUtils.eventually({
+            val activeNotifications: List<StatusBarNotification> =
+                listOf(*notificationService.activeNotifications)
+            Assert.assertFalse(
+                activeNotifications.any { notifications.contains(it) }
+            )
+        }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+    }
+
+    /**
+     * Get all notifications associated with a given package that are currently visible.
+     * @param pkg Package for which to filter the notifications by
+     * @return [List] of [StatusBarNotification]
+     */
+    @Throws(Exception::class)
+    private fun getNotificationsForPackage(pkg: String): List<StatusBarNotification> {
+        val notificationService = NotificationListener.getInstance()
+        val notifications: MutableList<StatusBarNotification> = ArrayList()
+        for (notification in notificationService.activeNotifications) {
+            if (notification.packageName == pkg) {
+                notifications.add(notification)
+            }
+        }
+        return notifications
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt b/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt
new file mode 100644
index 0000000..7514079
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SafetyCenterUtils.kt
@@ -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.permission.cts
+
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Build
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterManager
+import android.support.test.uiautomator.By
+import androidx.annotation.RequiresApi
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
+import com.android.safetycenter.internaldata.SafetyCenterIds
+import com.android.safetycenter.internaldata.SafetyCenterIssueId
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey
+import org.junit.Assert
+
+object SafetyCenterUtils {
+    /** Name of the flag that determines whether SafetyCenter is enabled. */
+    const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    /** Returns whether the device supports Safety Center. */
+    @JvmStatic
+    fun deviceSupportsSafetyCenter(context: Context): Boolean {
+        return context.resources.getBoolean(
+            Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android"))
+    }
+
+    /** Enabled or disable Safety Center */
+    @JvmStatic
+    fun setSafetyCenterEnabled(enabled: Boolean) {
+        setDeviceConfigPrivacyProperty(PROPERTY_SAFETY_CENTER_ENABLED, enabled.toString())
+    }
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    @JvmStatic
+    fun startSafetyCenterActivity(context: Context) {
+        context.startActivity(
+            Intent(Intent.ACTION_SAFETY_CENTER)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+    }
+
+    @JvmStatic
+    fun assertSafetyCenterStarted() {
+        // CollapsingToolbar title can't be found by text, so using description instead.
+        waitFindObject(By.desc("Security & privacy"))
+    }
+
+    @JvmStatic
+    fun setDeviceConfigPrivacyProperty(
+        propertyName: String,
+        value: String,
+        uiAutomation: UiAutomation = instrumentation.uiAutomation
+    ) {
+        runWithShellPermissionIdentity(uiAutomation) {
+            val valueWasSet =
+                DeviceConfig.setProperty(
+                    DeviceConfig.NAMESPACE_PRIVACY,
+                    /* name = */ propertyName,
+                    /* value = */ value,
+                    /* makeDefault = */ false)
+            check(valueWasSet) { "Could not set $propertyName to $value" }
+        }
+    }
+
+    @JvmStatic
+    fun deleteDeviceConfigPrivacyProperty(
+        propertyName: String,
+        uiAutomation: UiAutomation = instrumentation.uiAutomation
+    ) {
+        runWithShellPermissionIdentity(uiAutomation) {
+            DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_PRIVACY, propertyName)
+        }
+    }
+
+    @JvmStatic
+    private fun getSafetyCenterIssues(
+        automation: UiAutomation = instrumentation.uiAutomation
+    ): List<SafetyCenterIssue> {
+        val safetyCenterManager =
+            instrumentation.targetContext.getSystemService(SafetyCenterManager::class.java)
+        val issues = ArrayList<SafetyCenterIssue>()
+        runWithShellPermissionIdentity(automation) {
+            val safetyCenterData = safetyCenterManager!!.safetyCenterData
+            issues.addAll(safetyCenterData.issues)
+        }
+        return issues
+    }
+
+    @JvmStatic
+    fun assertSafetyCenterIssueExist(
+        sourceId: String,
+        issueId: String,
+        issueTypeId: String,
+        automation: UiAutomation = instrumentation.uiAutomation
+    ) {
+        val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+        Assert.assertTrue(
+            "Expect issues in safety center",
+            getSafetyCenterIssues(automation).any { safetyCenterIssueId == it.id })
+    }
+
+    @JvmStatic
+    fun assertSafetyCenterIssueDoesNotExist(
+        sourceId: String,
+        issueId: String,
+        issueTypeId: String,
+        automation: UiAutomation = instrumentation.uiAutomation
+    ) {
+        val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+        Assert.assertTrue(
+            "Expect no issue in safety center",
+            getSafetyCenterIssues(automation).none { safetyCenterIssueId == it.id })
+    }
+
+    private fun safetyCenterIssueId(sourceId: String, sourceIssueId: String, issueTypeId: String) =
+        SafetyCenterIds.encodeToString(
+            SafetyCenterIssueId.newBuilder()
+                .setSafetyCenterIssueKey(
+                    SafetyCenterIssueKey.newBuilder()
+                        .setSafetySourceId(sourceId)
+                        .setSafetySourceIssueId(sourceIssueId)
+                        .setUserId(UserHandle.myUserId())
+                        .build())
+                .setIssueTypeId(issueTypeId)
+                .build())
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
index 31e5449..367ae31 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -22,13 +22,11 @@
 import android.content.Intent
 import android.location.LocationManager
 import android.os.Build
-import android.provider.DeviceConfig
 import android.support.test.uiautomator.By
 import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.AppOpsUtils.setOpMode
 import com.android.compatibility.common.util.CtsDownstreamingTest
 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
-import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
@@ -47,7 +45,6 @@
     private val micLabel = packageManager.getPermissionGroupInfo(
         android.Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
     val locationManager = context.getSystemService(LocationManager::class.java)!!
-    private var wasEnabled = false
 
     @Before
     fun installAppLocationProviderAndAllowMockLocation() {
@@ -63,15 +60,11 @@
         setOpMode(
             context.packageName, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED
         )
-        wasEnabled = setSubattributionEnabledStateIfNeeded(true)
     }
 
     @After
     fun teardown() {
         locationManager.removeTestProvider(APP_PACKAGE_NAME)
-        if (!wasEnabled) {
-            setSubattributionEnabledStateIfNeeded(false)
-        }
     }
 
     @Test
@@ -115,24 +108,10 @@
         assertEquals(Activity.RESULT_OK, result.resultCode)
     }
 
-    private fun setSubattributionEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
-        var currentlyEnabled = false
-        runWithShellPermissionIdentity {
-            currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                FLAG_SUBATTRIBUTION, false)
-            if (currentlyEnabled != shouldBeEnabled) {
-                DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, FLAG_SUBATTRIBUTION,
-                    shouldBeEnabled.toString(), false)
-            }
-        }
-        return currentlyEnabled
-    }
-
     companion object {
         const val APP_APK_PATH = "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
         const val APP_PACKAGE_NAME = "android.permission3.cts.accessmicrophoneapplocationprovider"
         const val APP_LABEL = "LocationProviderWithMicApp"
         const val ATTRIBUTION_LABEL = "Attribution Label"
-        const val FLAG_SUBATTRIBUTION = "permissions_hub_subattribution_enabled"
     }
 }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
index 5355fdc..9e8b3a0 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
@@ -28,8 +28,8 @@
 import org.junit.After
 import org.junit.Assume.assumeFalse
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
+import java.util.regex.Pattern
 
 private const val APP_LABEL_1 = "CtsMicAccess"
 private const val APP_LABEL_2 = "CtsMicAccess2"
@@ -42,14 +42,14 @@
 private const val SHOW_7_DAYS = "Show 7 days"
 private const val SHOW_24_HOURS = "Show 24 hours"
 private const val MORE_OPTIONS = "More options"
-private const val TIMELINE_7_DAYS_DESCRIPTION = "in the past 7 days"
-private const val DASHBOARD_7_DAYS_DESCRIPTION = "7 days"
+private const val DASHBOARD_7_DAYS_DESCRIPTION_REGEX = "^.*7.*days$"
 private const val PRIV_DASH_7_DAY_ENABLED = "privacy_dashboard_7_day_toggle"
+private const val REFRESH = "Refresh"
 
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
 class PermissionHistoryTest : BasePermissionHubTest() {
     private val micLabel = packageManager.getPermissionGroupInfo(
-        Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+            Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
     private var was7DayToggleEnabled = false
 
     // Permission history is not available on TV devices.
@@ -100,15 +100,27 @@
         waitFindObject(By.textContains(APP_LABEL_1))
 
         openPermissionDashboard()
-        waitFindObject(By.res("android:id/title").textContains("Microphone")).click()
-        waitFindObject(By.textContains(micLabel))
-        waitFindObject(By.textContains(APP_LABEL_1))
+
+        SystemUtil.eventually {
+            try {
+                waitFindObject(By.hasChild(By.textContains("Microphone"))
+                        .hasChild(By.textStartsWith("Used by")))
+                        .click()
+                waitFindObject(By.textContains(micLabel))
+                waitFindObject(By.textContains(APP_LABEL_1))
+            } catch (e: Exception) {
+                // Sometimes the dashboard was in the state from previous failed tests.
+                // Clicking the refresh button to get the most recent access.
+                waitFindObject(By.descContains(REFRESH)).click()
+                throw e
+            }
+        }
+
         pressBack()
         pressBack()
     }
 
     @Test
-    @Ignore
     fun testToggleSystemApps() {
         // I had some hard time mocking a system app.
         // Hence here I am only testing if the toggle is there.
@@ -121,10 +133,18 @@
         // Auto doesn't show the "Show system" action when it is disabled. If a system app ends up
         // being installed for this test, then the Auto logic should be tested too.
         if (!isAutomotive) {
-            val menuView = waitFindObject(By.descContains(MORE_OPTIONS))
-            menuView.click()
-
-            waitFindObject(By.text(SHOW_SYSTEM))
+            SystemUtil.eventually {
+                try {
+                    val menuView = waitFindObject(By.descContains(MORE_OPTIONS))
+                    menuView.click()
+                    waitFindObject(By.text(SHOW_SYSTEM))
+                } catch (e: Exception) {
+                    // Sometimes the dashboard was in the state from previous failed tests.
+                    // Clicking the refresh button to get the most recent access.
+                    waitFindObject(By.descContains(REFRESH)).click()
+                    throw e
+                }
+            }
         }
 
         pressBack()
@@ -152,14 +172,24 @@
             waitFindObject(By.text(SHOW_7_DAYS)).click()
         }
 
-        waitFindObject(By.res("android:id/title").textContains("Microphone"))
-        waitFindObject(By.textContains(DASHBOARD_7_DAYS_DESCRIPTION))
+        SystemUtil.eventually {
+            try {
+                waitFindObject(By.hasChild(By.textContains("Microphone"))
+                        .hasChild(By.textStartsWith("Used by")))
+            } catch (e: Exception) {
+                // Sometimes the dashboard was in the state from previous failed tests.
+                // Clicking the refresh button to get the most recent access.
+                waitFindObject(By.descContains(REFRESH)).click()
+                throw e
+            }
+        }
+
+        waitFindObject(By.text(Pattern.compile(DASHBOARD_7_DAYS_DESCRIPTION_REGEX, Pattern.DOTALL)))
 
         pressBack()
     }
 
     @Test
-    @Ignore
     fun testToggleFrom24HoursTo7DaysInTimeline() {
         // Auto doesn't support the 7 day view
         assumeFalse(isAutomotive)
@@ -182,13 +212,12 @@
 
         waitFindObject(By.descContains(micLabel))
         waitFindObject(By.textContains(APP_LABEL_1))
-        waitFindObject(By.textContains(TIMELINE_7_DAYS_DESCRIPTION))
+        waitFindObject(By.text(Pattern.compile(DASHBOARD_7_DAYS_DESCRIPTION_REGEX, Pattern.DOTALL)))
 
         pressBack()
     }
 
     @Test
-    @Ignore
     fun testMicrophoneTimelineWithOneApp() {
         openMicrophoneApp(INTENT_ACTION_1)
         waitFindObject(By.textContains(APP_LABEL_1))
@@ -204,7 +233,6 @@
     }
 
     @Test
-    @Ignore
     fun testCameraTimelineWithMultipleApps() {
         openMicrophoneApp(INTENT_ACTION_1)
         waitFindObject(By.textContains(APP_LABEL_1))
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index fe9037a..d5bd49b 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -26,13 +26,19 @@
 import android.hardware.camera2.CameraManager
 import android.os.Build
 import android.os.Process
+import android.os.SystemClock
 import android.permission.PermissionManager
 import android.provider.DeviceConfig
 import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
 import android.server.wm.WindowManagerStateHelper
 import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.StaleObjectException
 import android.support.test.uiautomator.UiDevice
+import android.support.test.uiautomator.UiObject2
 import android.support.test.uiautomator.UiSelector
+import androidx.annotation.RequiresApi
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.DisableAnimationRule
@@ -40,6 +46,7 @@
 import com.android.compatibility.common.util.SystemUtil.eventually
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils
 import org.junit.After
 import org.junit.Assert
 import org.junit.Assert.assertEquals
@@ -50,7 +57,6 @@
 import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -72,6 +78,8 @@
 private const val UNEXPECTED_TIMEOUT_MILLIS = 1000
 private const val TIMEOUT_MILLIS: Long = 20000
 private const val TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
+private const val MIC_LABEL_NAME = "microphone_toggle_label_qs"
+private const val CAMERA_LABEL_NAME = "camera_toggle_label_qs"
 
 class CameraMicIndicatorsPermissionTest {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -84,11 +92,9 @@
 
     private val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
     private val isCar = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    private val micLabel = getPermissionControllerString(MIC_LABEL_NAME)
+    private val cameraLabel = getPermissionControllerString(CAMERA_LABEL_NAME)
     private var wasEnabled = false
-    private val micLabel = packageManager.getPermissionGroupInfo(
-        Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
-    private val cameraLabel = packageManager.getPermissionGroupInfo(
-        Manifest.permission_group.CAMERA, 0).loadLabel(packageManager).toString()
     private var isScreenOn = false
     private var screenTimeoutBeforeTest: Long = 0L
 
@@ -102,7 +108,7 @@
 
     private val safetyCenterEnabled = callWithShellPermissionIdentity {
         DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
-                SAFETY_CENTER_ENABLED, false.toString())
+            SAFETY_CENTER_ENABLED, false.toString())
     }
 
     @Before
@@ -115,7 +121,7 @@
                 context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 1800000L
             )
             DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
-                    SAFETY_CENTER_ENABLED, false.toString(), false)
+                SAFETY_CENTER_ENABLED, false.toString(), false)
         }
 
         if (!isScreenOn) {
@@ -207,9 +213,7 @@
         testCameraAndMicIndicator(useMic = false, useCamera = true, chainUsage = true)
     }
 
-    // Enable when safety center sends a broadcast on safety center flag value change
     @Test
-    @Ignore
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
     fun testSafetyCenterCameraIndicator() {
         assumeFalse(isTv)
@@ -217,28 +221,27 @@
         val manager = context.getSystemService(CameraManager::class.java)!!
         assumeTrue(manager.cameraIdList.isNotEmpty())
         changeSafetyCenterFlag(true.toString())
+        assumeSafetyCenterEnabled()
         testCameraAndMicIndicator(useMic = false, useCamera = true, safetyCenterEnabled = true)
     }
 
-    // Enable when safety center sends a broadcast on safety center flag value change
     @Test
-    @Ignore
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
     fun testSafetyCenterMicIndicator() {
         assumeFalse(isTv)
         assumeFalse(isCar)
         changeSafetyCenterFlag(true.toString())
+        assumeSafetyCenterEnabled()
         testCameraAndMicIndicator(useMic = true, useCamera = false, safetyCenterEnabled = true)
     }
 
-    // Enable when safety center sends a broadcast on safety center flag value change
     @Test
-    @Ignore
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
     fun testSafetyCenterHotwordIndicatorBehavior() {
         assumeFalse(isTv)
         assumeFalse(isCar)
         changeSafetyCenterFlag(true.toString())
+        assumeSafetyCenterEnabled()
         testCameraAndMicIndicator(
             useMic = false,
             useCamera = false,
@@ -247,14 +250,13 @@
         )
     }
 
-    // Enable when safety center sends a broadcast on safety center flag value change
     @Test
-    @Ignore
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
     fun testSafetyCenterChainUsageWithOtherUsage() {
         assumeFalse(isTv)
         assumeFalse(isCar)
         changeSafetyCenterFlag(true.toString())
+        assumeSafetyCenterEnabled()
         testCameraAndMicIndicator(
             useMic = false,
             useCamera = true,
@@ -405,15 +407,23 @@
                 return@eventually
             }
             if (useMic) {
-                val iconView = uiDevice.findObject(UiSelector().descriptionContains(micLabel))
-                assertTrue("View with description $micLabel not found", iconView.exists())
+                var iconView = if (safetyCenterEnabled) {
+                    waitFindObjectOrNull(By.text(micLabel))
+                } else {
+                    uiDevice.findObject(UiSelector().descriptionContains(micLabel))
+                }
+                assertNotNull("View with description $micLabel not found", iconView)
             }
             if (useCamera) {
-                val iconView = uiDevice.findObject(UiSelector().descriptionContains(cameraLabel))
-                assertTrue("View with text $APP_LABEL not found", iconView.exists())
+                var iconView = if (safetyCenterEnabled) {
+                    waitFindObjectOrNull(By.text(cameraLabel))
+                } else {
+                    uiDevice.findObject(UiSelector().descriptionContains(cameraLabel))
+                }
+                assertNotNull("View with text $APP_LABEL not found", iconView)
             }
-            val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
-            assertTrue("View with text $APP_LABEL not found", appView.exists())
+            var appView = waitFindObjectOrNull(By.textContains(APP_LABEL))
+            assertNotNull("View with text $APP_LABEL not found", appView)
             if (safetyCenterEnabled) {
                 assertTrue("Did not find safety center views",
                     uiDevice.findObjects(By.res(SAFETY_CENTER_ITEM_ID)).size > 0)
@@ -445,16 +455,21 @@
             "Did not find shell package"
         }
 
-        val usageViews = if (safetyCenterEnabled) {
-            uiDevice.findObjects(By.res(SAFETY_CENTER_ITEM_ID))
+        if (safetyCenterEnabled) {
+            var micView = waitFindObjectOrNull(By.text(micLabel))
+            assertNotNull("View with text $micLabel not found", micView)
+            var camView = waitFindObjectOrNull(By.text(cameraLabel))
+            assertNotNull("View with text $cameraLabel not found", camView)
+            var shellView = waitFindObjectOrNull(By.textContains(shellLabel))
+            assertNotNull("View with text $shellLabel not found", shellView)
         } else {
-            uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+            val usageViews = uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+            assertEquals("Expected two usage views", 2, usageViews.size)
+            val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
+            assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
+            val shellView = uiDevice.findObjects(By.textContains(shellLabel))
+            assertEquals("Expected only one shell view", 1, shellView.size)
         }
-        assertEquals("Expected two usage views", 2, usageViews.size)
-        val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
-        assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
-        val shellView = uiDevice.findObjects(By.textContains(shellLabel))
-        assertEquals("Expected only one shell view", 1, shellView.size)
     }
 
     private fun isChipPresent(): Boolean {
@@ -488,7 +503,52 @@
     private fun changeSafetyCenterFlag(safetyCenterEnabled: String) {
         runWithShellPermissionIdentity {
             DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
-                    SAFETY_CENTER_ENABLED, safetyCenterEnabled, false)
+                SAFETY_CENTER_ENABLED, safetyCenterEnabled, false)
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    private fun assumeSafetyCenterEnabled() {
+        val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+        val isSafetyCenterEnabled: Boolean = runWithShellPermissionIdentity<Boolean> {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+        assumeTrue(isSafetyCenterEnabled)
+    }
+
+    protected fun waitFindObjectOrNull(selector: BySelector): UiObject2? {
+        waitForIdle()
+        return findObjectWithRetry({ t -> UiAutomatorUtils.waitFindObjectOrNull(selector, t) })
+    }
+
+    private fun findObjectWithRetry(
+        automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+        timeoutMillis: Long = TIMEOUT_MILLIS
+    ): UiObject2? {
+        waitForIdle()
+        val startTime = SystemClock.elapsedRealtime()
+        return try {
+            automatorMethod(timeoutMillis)
+        } catch (e: StaleObjectException) {
+            val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+            if (remainingTime <= 0) {
+                throw e
+            }
+            automatorMethod(remainingTime)
+        }
+    }
+
+    private fun getPermissionControllerString(resourceName: String): String {
+        val permissionControllerPkg = context.packageManager.permissionControllerPackageName
+        try {
+            val permissionControllerContext =
+                context.createPackageContext(permissionControllerPkg, 0)
+            val resourceId =
+                permissionControllerContext.resources.getIdentifier(
+                    resourceName, "string", "com.android.permissioncontroller")
+            return permissionControllerContext.getString(resourceId)
+        } catch (e: PackageManager.NameNotFoundException) {
+            throw RuntimeException(e)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
index 1e318ea..7c9a6b2 100644
--- a/tests/tests/provider/OWNERS
+++ b/tests/tests/provider/OWNERS
@@ -1,7 +1,13 @@
-# Bug component: 655625
-
-include platform/frameworks/base:/core/java/android/os/storage/OWNERS
-
 tgunn@google.com
 nicksauer@google.com
 nona@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/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 26b5b7f..d063b40 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -629,7 +629,7 @@
      * then the attach/destroy will not correspond to enable/disable and will not result in a new
      * MAC address being generated.
      */
-    public void testAttachDiscoveryAddressChanges() {
+    public void testAttachDiscoveryAddressChanges() throws InterruptedException {
         if (!TestUtils.shouldTestWifiAware(getContext())) {
             return;
         }
@@ -638,6 +638,7 @@
         Set<TestUtils.MacWrapper> macs = new HashSet<>();
 
         for (int i = 0; i < numIterations; ++i) {
+            Thread.sleep(1000);
             AttachCallbackTest attachCb = new AttachCallbackTest();
             IdentityChangedListenerTest identityL = new IdentityChangedListenerTest();
             mWifiAwareManager.attach(attachCb, identityL, mHandler);
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index f7322dd..fad09f4 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -50,6 +50,7 @@
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -274,6 +275,31 @@
                 new LinkedList<Integer>(Arrays.asList(waitSingleSync)));
     }
 
+    private NetworkInfo.DetailedState waitForNextNetworkState() {
+        assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+        assertNotNull(mMySync.expectedNetworkInfo);
+        return mMySync.expectedNetworkInfo.getDetailedState();
+    }
+
+    private boolean waitForConnectedNetworkState() {
+        // The possible orders of network states are:
+        // * IDLE > CONNECTING > CONNECTED for lazy initialization
+        // * DISCONNECTED > CONNECTING > CONNECTED for previous group removal
+        // * CONNECTING > CONNECTED
+        NetworkInfo.DetailedState state = waitForNextNetworkState();
+        if (state == NetworkInfo.DetailedState.IDLE
+                || state == NetworkInfo.DetailedState.DISCONNECTED) {
+            state = waitForNextNetworkState();
+        }
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            if (state != NetworkInfo.DetailedState.CONNECTING) {
+                return false;
+            }
+            state = waitForNextNetworkState();
+        }
+        return state == NetworkInfo.DetailedState.CONNECTED;
+    }
+
     private boolean waitForServiceResponse(MyResponse waitResponse) {
         synchronized (waitResponse) {
             long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
@@ -508,21 +534,7 @@
         mWifiP2pManager.createGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
-
-        // The first network state might be IDLE due to
-        // lazy initialization, but not CONNECTED.
-        for (int i = 0; i < 2; i++) {
-            assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
-            assertNotNull(mMySync.expectedNetworkInfo);
-            if (NetworkInfo.DetailedState.CONNECTED ==
-                    mMySync.expectedNetworkInfo.getDetailedState()) {
-                break;
-            }
-            assertEquals(NetworkInfo.DetailedState.IDLE,
-                    mMySync.expectedNetworkInfo.getDetailedState());
-        }
-        assertEquals(NetworkInfo.DetailedState.CONNECTED,
-                mMySync.expectedNetworkInfo.getDetailedState());
+        assertTrue(waitForConnectedNetworkState());
 
         resetResponse(mMyResponse);
         mWifiP2pManager.requestNetworkInfo(mWifiP2pChannel,
@@ -660,21 +672,7 @@
         mWifiP2pManager.createGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
-
-        // The first network state might be IDLE due to
-        // lazy initialization, but not CONNECTED.
-        for (int i = 0; i < 2; i++) {
-            assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
-            assertNotNull(mMySync.expectedNetworkInfo);
-            if (NetworkInfo.DetailedState.CONNECTED ==
-                    mMySync.expectedNetworkInfo.getDetailedState()) {
-                break;
-            }
-            assertEquals(NetworkInfo.DetailedState.IDLE,
-                    mMySync.expectedNetworkInfo.getDetailedState());
-        }
-        assertEquals(NetworkInfo.DetailedState.CONNECTED,
-                mMySync.expectedNetworkInfo.getDetailedState());
+        assertTrue(waitForConnectedNetworkState());
 
         resetResponse(mMyResponse);
         mWifiP2pManager.removeGroup(mWifiP2pChannel, mActionListener);
@@ -707,10 +705,7 @@
         mWifiP2pManager.createGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
-        assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
-        assertNotNull(mMySync.expectedNetworkInfo);
-        assertEquals(NetworkInfo.DetailedState.CONNECTED,
-                mMySync.expectedNetworkInfo.getDetailedState());
+        assertTrue(waitForConnectedNetworkState());
 
         resetResponse(mMyResponse);
         mWifiP2pManager.removeGroup(mWifiP2pChannel, mActionListener);
@@ -812,21 +807,7 @@
         mWifiP2pManager.createGroup(mWifiP2pChannel, mActionListener);
         assertTrue(waitForServiceResponse(mMyResponse));
         assertTrue(mMyResponse.success);
-
-        // The first network state might be IDLE due to
-        // lazy initialization, but not CONNECTED.
-        for (int i = 0; i < 2; i++) {
-            assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
-            assertNotNull(mMySync.expectedNetworkInfo);
-            if (NetworkInfo.DetailedState.CONNECTED
-                    == mMySync.expectedNetworkInfo.getDetailedState()) {
-                break;
-            }
-            assertEquals(NetworkInfo.DetailedState.IDLE,
-                    mMySync.expectedNetworkInfo.getDetailedState());
-        }
-        assertEquals(NetworkInfo.DetailedState.CONNECTED,
-                mMySync.expectedNetworkInfo.getDetailedState());
+        assertTrue(waitForConnectedNetworkState());
 
         resetResponse(mMyResponse);
         MacAddress peerMacAddress = MacAddress.fromString(mTestWifiP2pPeerConfig.deviceAddress);
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index e3ff5f8..474d488 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -97,7 +97,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.FeatureUtil;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.PropertyUtil;
@@ -1881,6 +1880,7 @@
         TestExecutor executor = new TestExecutor();
         TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        setWifiEnabled(false);
         boolean wifiEnabled = mWifiManager.isWifiEnabled();
         try {
             uiAutomation.adoptShellPermissionIdentity();
@@ -2912,13 +2912,6 @@
                 verifySetGetSoftApConfig(softApConfigBuilder.build());
             }
 
-            // Test 11 AX control config.
-            if (callback.getCurrentSoftApCapability()
-                    .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
-                softApConfigBuilder.setIeee80211axEnabled(true);
-                verifySetGetSoftApConfig(softApConfigBuilder.build());
-            }
-
             // Test 11 BE control config
             if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
                 if (callback.getCurrentSoftApCapability()
@@ -2929,6 +2922,12 @@
             }
 
             if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
+                // Test 11 AX control config.
+                if (callback.getCurrentSoftApCapability()
+                        .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
+                    softApConfigBuilder.setIeee80211axEnabled(true);
+                    verifySetGetSoftApConfig(softApConfigBuilder.build());
+                }
                 softApConfigBuilder.setBridgedModeOpportunisticShutdownEnabled(false);
                 verifySetGetSoftApConfig(softApConfigBuilder.build());
             }
@@ -3912,6 +3911,14 @@
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public void testActiveCountryCodeChangedCallback() throws Exception {
+        if (!hasLocationFeature()) {
+            // skip the test if location is not supported
+            return;
+        }
+        if (!isLocationEnabled()) {
+            fail("Please enable location for this test - since country code is not available"
+                    + " when location is disabled!");
+        }
         TestActiveCountryCodeChangedCallback testCountryCodeChangedCallback =
                 new TestActiveCountryCodeChangedCallback();
         TestExecutor executor = new TestExecutor();
@@ -5469,7 +5476,6 @@
     /**
      * Tests {@link WifiConfiguration#setBssidAllowlist(List)}.
      */
-    @ApiTest(apis = "android.net.wifi.WifiConfiguration#setBssidAllowlist")
     public void testBssidAllowlist() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
diff --git a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
deleted file mode 100644
index 6fcdc74..0000000
--- a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
+++ /dev/null
@@ -1,629 +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.uwb.cts;
-
-import static android.uwb.RangingSession.Callback.REASON_BAD_PARAMETERS;
-
-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 static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.uwb.IUwbAdapter;
-import android.uwb.RangingReport;
-import android.uwb.RangingSession;
-import android.uwb.SessionHandle;
-import android.uwb.UwbAddress;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.concurrent.Executor;
-
-/**
- * Test of {@link RangingSession}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RangingSessionTest {
-    private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
-    private static final PersistableBundle PARAMS = new PersistableBundle();
-    private static final UwbAddress UWB_ADDRESS = UwbAddress.fromBytes(new byte[] {0x00, 0x56});
-    private static final @RangingSession.Callback.Reason int REASON =
-            RangingSession.Callback.REASON_GENERIC_ERROR;
-
-    @Test
-    public void testOnRangingOpened_OnOpenSuccessCalled() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        verifyOpenState(session, false);
-
-        session.onRangingOpened();
-        verifyOpenState(session, true);
-
-        // Verify that the onOpenSuccess callback was invoked
-        verify(callback, times(1)).onOpened(eq(session));
-        verify(callback, times(0)).onClosed(anyInt(), any());
-    }
-
-    @Test
-    public void testOnRangingOpened_OnServiceDiscoveredConnectedCalled() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        verifyOpenState(session, false);
-
-        session.onRangingOpened();
-        verifyOpenState(session, true);
-
-        // Verify that the onOpenSuccess callback was invoked
-        verify(callback, times(1)).onOpened(eq(session));
-        verify(callback, times(0)).onClosed(anyInt(), any());
-
-        session.onServiceDiscovered(PARAMS);
-        verify(callback, times(1)).onServiceDiscovered(eq(PARAMS));
-
-        session.onServiceConnected(PARAMS);
-        verify(callback, times(1)).onServiceConnected(eq(PARAMS));
-    }
-
-
-    @Test
-    public void testOnRangingOpened_CannotOpenClosedSession() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
-        session.onRangingOpened();
-        verifyOpenState(session, true);
-        verify(callback, times(1)).onOpened(eq(session));
-        verify(callback, times(0)).onClosed(anyInt(), any());
-
-        session.onRangingClosed(REASON, PARAMS);
-        verifyOpenState(session, false);
-        verify(callback, times(1)).onOpened(eq(session));
-        verify(callback, times(1)).onClosed(anyInt(), any());
-
-        // Now invoke the ranging started callback and ensure the session remains closed
-        session.onRangingOpened();
-        verifyOpenState(session, false);
-        verify(callback, times(1)).onOpened(eq(session));
-        verify(callback, times(1)).onClosed(anyInt(), any());
-    }
-
-    @Test
-    public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        verifyOpenState(session, false);
-
-        session.onRangingClosed(REASON, PARAMS);
-        verifyOpenState(session, false);
-
-        // Verify that the onOpenSuccess callback was invoked
-        verify(callback, times(0)).onOpened(eq(session));
-        verify(callback, times(1)).onClosed(anyInt(), any());
-    }
-
-    @Test
-    public void testOnRangingClosed_OnClosedCalled() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        session.onRangingStarted(PARAMS);
-        session.onRangingClosed(REASON, PARAMS);
-        verify(callback, times(1)).onClosed(anyInt(), any());
-
-        verifyOpenState(session, false);
-        session.onRangingClosed(REASON, PARAMS);
-        verify(callback, times(2)).onClosed(anyInt(), any());
-    }
-
-    @Test
-    public void testOnRangingResult_OnReportReceivedCalled() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        verifyOpenState(session, false);
-
-        session.onRangingStarted(PARAMS);
-        verifyOpenState(session, true);
-
-        RangingReport report = UwbTestUtils.getRangingReports(1);
-        session.onRangingResult(report);
-        verify(callback, times(1)).onReportReceived(eq(report));
-    }
-
-    @Test
-    public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
-        session.onRangingOpened();
-
-        session.start(PARAMS);
-        verify(callback, times(1)).onStarted(any());
-
-        // Calling start again should throw an illegal state
-        verifyThrowIllegalState(() -> session.start(PARAMS));
-        verify(callback, times(1)).onStarted(any());
-    }
-
-    @Test
-    public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
-        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
-        session.onRangingOpened();
-        session.start(PARAMS);
-
-        verifyNoThrowIllegalState(session::stop);
-        verify(callback, times(1)).onStopped(anyInt(), any());
-
-        // Calling stop again should throw an illegal state
-        verifyThrowIllegalState(session::stop);
-        verify(callback, times(1)).onStopped(anyInt(), any());
-    }
-
-    @Test
-    public void testStop_CannotStopIfOpenFailed() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
-        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
-        session.onRangingOpened();
-        session.start(PARAMS);
-
-        verifyNoThrowIllegalState(() -> session.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS));
-        verify(callback, times(1)).onOpenFailed(
-                REASON_BAD_PARAMETERS, PARAMS);
-
-        // Calling stop again should throw an illegal state
-        verifyThrowIllegalState(session::stop);
-        verify(callback, times(0)).onStopped(anyInt(), any());
-    }
-
-    @Test
-    public void testCallbacks_OnlyWhenOpened() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new OpenAnswer(session)).when(adapter).openRanging(
-                any(), any(), any(), any(), any());
-        doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
-        doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
-        doAnswer(new PauseAnswer(session)).when(adapter).pause(any(), any());
-        doAnswer(new ResumeAnswer(session)).when(adapter).resume(any(), any());
-        doAnswer(new ControleeAddAnswer(session)).when(adapter).addControlee(any(), any());
-        doAnswer(new ControleeRemoveAnswer(session)).when(adapter).removeControlee(any(), any());
-        doAnswer(new DataSendAnswer(session)).when(adapter).sendData(any(), any(), any(), any());
-        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
-        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
-
-        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verify(callback, times(0)).onReconfigured(any());
-        verifyOpenState(session, false);
-
-        session.onRangingOpened();
-        verifyOpenState(session, true);
-        verify(callback, times(1)).onOpened(any());
-        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verify(callback, times(1)).onReconfigured(any());
-        verifyThrowIllegalState(() -> session.pause(PARAMS));
-        verify(callback, times(0)).onPaused(any());
-        verifyThrowIllegalState(() -> session.resume(PARAMS));
-        verify(callback, times(0)).onResumed(any());
-        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
-        verify(callback, times(1)).onControleeAdded(any());
-        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
-        verify(callback, times(1)).onControleeRemoved(any());
-        verifyThrowIllegalState(() -> session.sendData(
-                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
-        verify(callback, times(0)).onDataSent(any(), any());
-
-        session.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
-        verifyOpenState(session, true);
-        verify(callback, times(1)).onStartFailed(
-                REASON_BAD_PARAMETERS, PARAMS);
-
-        session.onRangingStarted(PARAMS);
-        verifyOpenState(session, true);
-        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verify(callback, times(2)).onReconfigured(any());
-        verifyNoThrowIllegalState(() -> session.reconfigure(null));
-        verify(callback, times(1)).onReconfigureFailed(
-                eq(REASON_BAD_PARAMETERS), any());
-        verifyNoThrowIllegalState(() -> session.pause(PARAMS));
-        verify(callback, times(1)).onPaused(any());
-        verifyNoThrowIllegalState(() -> session.pause(null));
-        verify(callback, times(1)).onPauseFailed(
-                eq(REASON_BAD_PARAMETERS), any());
-        verifyNoThrowIllegalState(() -> session.resume(PARAMS));
-        verify(callback, times(1)).onResumed(any());
-        verifyNoThrowIllegalState(() -> session.resume(null));
-        verify(callback, times(1)).onResumeFailed(
-                eq(REASON_BAD_PARAMETERS), any());
-        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
-        verify(callback, times(2)).onControleeAdded(any());
-        verifyNoThrowIllegalState(() -> session.addControlee(null));
-        verify(callback, times(1)).onControleeAddFailed(
-                eq(REASON_BAD_PARAMETERS), any());
-        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
-        verify(callback, times(2)).onControleeRemoved(any());
-        verifyNoThrowIllegalState(() -> session.removeControlee(null));
-        verify(callback, times(1)).onControleeRemoveFailed(
-                eq(REASON_BAD_PARAMETERS), any());
-        verifyNoThrowIllegalState(() -> session.sendData(
-                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
-        verify(callback, times(1)).onDataSent(any(), any());
-        verifyNoThrowIllegalState(() -> session.sendData(
-                null, PARAMS, new byte[] {0x05, 0x1}));
-        verify(callback, times(1)).onDataSendFailed(
-                eq(null), eq(REASON_BAD_PARAMETERS), any());
-
-        session.onDataReceived(UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
-        verify(callback, times(1)).onDataReceived(
-                UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
-        session.onDataReceiveFailed(UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
-        verify(callback, times(1)).onDataReceiveFailed(
-                UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
-
-        session.stop();
-        verifyOpenState(session, true);
-        verify(callback, times(1)).onStopped(REASON, PARAMS);
-
-        verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verify(callback, times(3)).onReconfigured(any());
-        verifyThrowIllegalState(() -> session.pause(PARAMS));
-        verify(callback, times(1)).onPaused(any());
-        verifyThrowIllegalState(() -> session.resume(PARAMS));
-        verify(callback, times(1)).onResumed(any());
-        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
-        verify(callback, times(3)).onControleeAdded(any());
-        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
-        verify(callback, times(3)).onControleeRemoved(any());
-        verifyThrowIllegalState(() -> session.sendData(
-                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
-        verify(callback, times(1)).onDataSent(any(), any());
-
-        session.close();
-        verifyOpenState(session, false);
-        verify(callback, times(1)).onClosed(REASON, PARAMS);
-
-        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verify(callback, times(3)).onReconfigured(any());
-        verifyThrowIllegalState(() -> session.pause(PARAMS));
-        verify(callback, times(1)).onPaused(any());
-        verifyThrowIllegalState(() -> session.resume(PARAMS));
-        verify(callback, times(1)).onResumed(any());
-        verifyThrowIllegalState(() -> session.addControlee(PARAMS));
-        verify(callback, times(3)).onControleeAdded(any());
-        verifyThrowIllegalState(() -> session.removeControlee(PARAMS));
-        verify(callback, times(3)).onControleeRemoved(any());
-        verifyThrowIllegalState(() -> session.sendData(
-                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
-        verify(callback, times(1)).onDataSent(any(), any());
-    }
-
-    @Test
-    public void testClose_NoCallbackUntilInvoked() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        session.onRangingOpened();
-
-        // Calling close multiple times should invoke closeRanging until the session receives
-        // the onClosed callback.
-        int totalCallsBeforeOnRangingClosed = 3;
-        for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
-            session.close();
-            verifyOpenState(session, true);
-            verify(adapter, times(i)).closeRanging(handle);
-            verify(callback, times(0)).onClosed(anyInt(), any());
-        }
-
-        // After onClosed is invoked, then the adapter should no longer be called for each call to
-        // the session's close.
-        final int totalCallsAfterOnRangingClosed = 2;
-        for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
-            session.onRangingClosed(REASON, PARAMS);
-            verifyOpenState(session, false);
-            verify(adapter, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
-            verify(callback, times(i)).onClosed(anyInt(), any());
-        }
-    }
-
-    @Test
-    public void testClose_OnClosedCalled() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
-        session.onRangingOpened();
-
-        session.close();
-        verify(callback, times(1)).onClosed(anyInt(), any());
-    }
-
-    @Test
-    public void testClose_CannotInteractFurther() throws RemoteException {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-        doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
-        session.close();
-
-        verifyThrowIllegalState(() -> session.start(PARAMS));
-        verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
-        verifyThrowIllegalState(() -> session.stop());
-        verifyNoThrowIllegalState(() -> session.close());
-    }
-
-    @Test
-    public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
-        assertFalse(session.isOpen());
-        session.onRangingStarted(PARAMS);
-        assertTrue(session.isOpen());
-
-        // Verify that the onReportReceived callback was invoked
-        RangingReport report = UwbTestUtils.getRangingReports(1);
-        session.onRangingResult(report);
-        verify(callback, times(1)).onReportReceived(report);
-    }
-
-    @Test
-    public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
-        SessionHandle handle = new SessionHandle(123);
-        RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
-        RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
-
-        assertFalse(session.isOpen());
-
-        // Verify that the onReportReceived callback was invoked
-        RangingReport report = UwbTestUtils.getRangingReports(1);
-        session.onRangingResult(report);
-        verify(callback, times(0)).onReportReceived(report);
-    }
-
-    private void verifyOpenState(RangingSession session, boolean expected) {
-        assertEquals(expected, session.isOpen());
-    }
-
-    private void verifyThrowIllegalState(Runnable runnable) {
-        try {
-            runnable.run();
-            fail();
-        } catch (IllegalStateException e) {
-            // Pass
-        }
-    }
-
-    private void verifyNoThrowIllegalState(Runnable runnable) {
-        try {
-            runnable.run();
-        } catch (IllegalStateException e) {
-            fail();
-        }
-    }
-
-    abstract class AdapterAnswer implements Answer {
-        protected RangingSession mSession;
-
-        protected AdapterAnswer(RangingSession session) {
-            mSession = session;
-        }
-    }
-
-    class OpenAnswer extends AdapterAnswer {
-        OpenAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onRangingOpened();
-            } else {
-                mSession.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class StartAnswer extends AdapterAnswer {
-        StartAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onRangingStarted(PARAMS);
-            } else {
-                mSession.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class ReconfigureAnswer extends AdapterAnswer {
-        ReconfigureAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onRangingReconfigured(PARAMS);
-            } else {
-                mSession.onRangingReconfigureFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class PauseAnswer extends AdapterAnswer {
-        PauseAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onRangingPaused(PARAMS);
-            } else {
-                mSession.onRangingPauseFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class ResumeAnswer extends AdapterAnswer {
-        ResumeAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onRangingResumed(PARAMS);
-            } else {
-                mSession.onRangingResumeFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class ControleeAddAnswer extends AdapterAnswer {
-        ControleeAddAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onControleeAdded(PARAMS);
-            } else {
-                mSession.onControleeAddFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class ControleeRemoveAnswer extends AdapterAnswer {
-        ControleeRemoveAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            PersistableBundle argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onControleeRemoved(PARAMS);
-            } else {
-                mSession.onControleeRemoveFailed(REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class DataSendAnswer extends AdapterAnswer {
-        DataSendAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            UwbAddress argParams = invocation.getArgument(1);
-            if (argParams != null) {
-                mSession.onDataSent(UWB_ADDRESS, PARAMS);
-            } else {
-                mSession.onDataSendFailed(null, REASON_BAD_PARAMETERS, PARAMS);
-            }
-            return null;
-        }
-    }
-
-    class StopAnswer extends AdapterAnswer {
-        StopAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            mSession.onRangingStopped(REASON, PARAMS);
-            return null;
-        }
-    }
-
-    class CloseAnswer extends AdapterAnswer {
-        CloseAnswer(RangingSession session) {
-            super(session);
-        }
-
-        @Override
-        public Object answer(InvocationOnMock invocation) {
-            mSession.onRangingClosed(REASON, PARAMS);
-            return null;
-        }
-    }
-}
diff --git a/tests/uwb/src/android/uwb/cts/SessionHandleTest.java b/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
deleted file mode 100644
index d52a3e7..0000000
--- a/tests/uwb/src/android/uwb/cts/SessionHandleTest.java
+++ /dev/null
@@ -1,53 +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.uwb.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-import android.uwb.SessionHandle;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test of {@link SessionHandle}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SessionHandleTest {
-
-    @Test
-    public void testBasic() {
-        int handleId = 12;
-        SessionHandle handle = new SessionHandle(handleId);
-        assertEquals(handle.getId(), handleId);
-    }
-
-    @Test
-    public void testParcel() {
-        Parcel parcel = Parcel.obtain();
-        SessionHandle handle = new SessionHandle(10);
-        handle.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        SessionHandle fromParcel = SessionHandle.CREATOR.createFromParcel(parcel);
-        assertEquals(handle, fromParcel);
-    }
-}