Merge "(ImsMedia) remove forking APIs based on vendor feedback" into main-ims-dev
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index d4c1e85..d6e6f50 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -148,6 +148,7 @@
     vendor_available: true,
     srcs: [
         "android/hardware/audio/effect/Descriptor.aidl",
+        "android/hardware/audio/effect/IEffect.aidl",
         "android/hardware/audio/effect/IFactory.aidl",
     ],
     imports: [
@@ -165,3 +166,19 @@
         },
     },
 }
+
+latest_android_hardware_audio_effect = "android.hardware.audio.effect-V1"
+
+cc_defaults {
+    name: "latest_android_hardware_audio_effect_ndk_shared",
+    shared_libs: [
+        latest_android_hardware_audio_effect + "-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "latest_android_hardware_audio_effect_ndk_static",
+    static_libs: [
+        latest_android_hardware_audio_effect + "-ndk",
+    ],
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl
index 918ae3a..7f868ad 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IEffect.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,10 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.audio.effect;
+@VintfStability
+interface IEffect {
+  void open();
+  void close();
+  android.hardware.audio.effect.Descriptor getDescriptor();
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl
index b6c9aab..db06475 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/IFactory.aidl
@@ -35,4 +35,6 @@
 @VintfStability
 interface IFactory {
   android.hardware.audio.effect.Descriptor.Identity[] queryEffects(in @nullable android.media.audio.common.AudioUuid type, in @nullable android.media.audio.common.AudioUuid implementation);
+  android.hardware.audio.effect.IEffect createEffect(in android.media.audio.common.AudioUuid implUuid);
+  void destroyEffect(in android.hardware.audio.effect.IEffect handle);
 }
diff --git a/audio/aidl/android/hardware/audio/effect/IEffect.aidl b/audio/aidl/android/hardware/audio/effect/IEffect.aidl
new file mode 100644
index 0000000..d7a9501
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/effect/IEffect.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.audio.effect;
+
+import android.hardware.audio.effect.Descriptor;
+
+/**
+ * Effect interfaces definitions to configure and control the effect instance.
+ */
+@VintfStability
+interface IEffect {
+    /**
+     * Open an effect instance, effect should not start processing data before receive START
+     * command. All necessary information should be allocated and instance should transfer to IDLE
+     * state after open() call has been handled successfully.
+     * After open, the effect instance should be able to handle all IEffect interface calls.
+     *
+     * @throws a EX_UNSUPPORTED_OPERATION if device capability/resource is not enough or system
+     *         failure happens.
+     * @note Open an already-opened effect instance should do nothing and should not throw an error.
+     */
+    void open();
+
+    /**
+     * Called by the client to close the effect instance, processing thread should be destroyed and
+     * consume no CPU after close.
+     *
+     * It is recommended to close the effect on the client side as soon as it becomes unused, it's
+     * client responsibility to make sure all parameter/buffer is correct if client wants to reopen
+     * a closed instance.
+     *
+     * Effect instance close interface should always succeed unless:
+     * 1. The effect instance is not in a proper state to be closed, for example it's still in
+     * processing state.
+     * 2. There is system/hardware related failure when close.
+     *
+     * @throws EX_ILLEGAL_STATE if the effect instance is not in a proper state to be closed.
+     * @throws EX_UNSUPPORTED_OPERATION if the effect instance failed to close for any other reason.
+     * @note Close an already-closed effect should do nothing and should not throw an error.
+     */
+    void close();
+
+    /**
+     * Return the @c Descriptor of this effect instance.
+     *
+     * Must be available for the effect instance at anytime and should always succeed.
+     *
+     * @return Descriptor The @c Descriptor of this effect instance.
+     */
+    Descriptor getDescriptor();
+}
diff --git a/audio/aidl/android/hardware/audio/effect/IFactory.aidl b/audio/aidl/android/hardware/audio/effect/IFactory.aidl
index 99c2e6a..4873d3a 100644
--- a/audio/aidl/android/hardware/audio/effect/IFactory.aidl
+++ b/audio/aidl/android/hardware/audio/effect/IFactory.aidl
@@ -17,6 +17,7 @@
 package android.hardware.audio.effect;
 
 import android.hardware.audio.effect.Descriptor;
+import android.hardware.audio.effect.IEffect;
 import android.media.audio.common.AudioUuid;
 
 /**
@@ -42,4 +43,30 @@
      */
     Descriptor.Identity[] queryEffects(
             in @nullable AudioUuid type, in @nullable AudioUuid implementation);
+
+    /**
+     * Called by the audio framework to create the effect (identified by the implementation UUID
+     * parameter).
+     *
+     * The effect instance should be able to maintain its own context and parameters after creation.
+     *
+     * @param implUuid UUID for the effect implementation which instance will be created based on.
+     * @return The effect instance handle created.
+     * @throws EX_ILLEGAL_ARGUMENT if the implUuid is not valid.
+     * @throws EX_TRANSACTION_FAILED if device capability/resource is not enough to create the
+     *         effect instance.
+     */
+    IEffect createEffect(in AudioUuid implUuid);
+
+    /**
+     * Called by the framework to destroy the effect and free up all currently allocated resources.
+     * It is recommended to destroy the effect from the client side as soon as it is becomes unused.
+     *
+     * The client must ensure effect instance is closed before destroy.
+     *
+     * @param handle The handle of effect instance to be destroyed.
+     * @throws EX_ILLEGAL_ARGUMENT if the effect handle is not valid.
+     * @throws EX_ILLEGAL_STATE if the effect instance is not in a proper state to be destroyed.
+     */
+    void destroyEffect(in IEffect handle);
 }
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 5d63347..53ed908 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -68,6 +68,7 @@
         "libbase",
         "libbinder_ndk",
         "android.hardware.audio.effect-V1-ndk",
+        "libequalizer",
     ],
     cflags: [
         "-Wall",
@@ -80,7 +81,10 @@
 cc_library_static {
     name: "libaudioeffectserviceexampleimpl",
     defaults: ["aidlaudioeffectservice_defaults"],
-    export_include_dirs: ["include"],
+    export_include_dirs: [
+        "include",
+        "include/equalizer-impl/",
+    ],
     srcs: [
         "EffectFactory.cpp",
     ],
diff --git a/audio/aidl/default/EffectFactory.cpp b/audio/aidl/default/EffectFactory.cpp
index a31e23f..a9848fd 100644
--- a/audio/aidl/default/EffectFactory.cpp
+++ b/audio/aidl/default/EffectFactory.cpp
@@ -29,19 +29,8 @@
     // TODO: implement this with xml parser on audio_effect.xml, and filter with optional
     // parameters.
     Descriptor::Identity id;
-    id.type = {static_cast<int32_t>(0x0bed4300),
-               0xddd6,
-               0x11db,
-               0x8f34,
-               {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
-    id.uuid = EqualizerUUID;
-    mIdentityList.push_back(id);
-    id.type = {static_cast<int32_t>(0xd3467faa),
-               0xacc7,
-               0x4d34,
-               0xacaf,
-               {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
-    id.uuid = VisualizerUUID;
+    id.type = EqualizerTypeUUID;
+    id.uuid = EqualizerSwImplUUID;
     mIdentityList.push_back(id);
 }
 
@@ -56,4 +45,28 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Factory::createEffect(
+        const AudioUuid& in_impl_uuid,
+        std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>* _aidl_return) {
+    LOG(DEBUG) << __func__ << ": UUID " << in_impl_uuid.toString();
+    if (in_impl_uuid == EqualizerSwImplUUID) {
+        *_aidl_return = ndk::SharedRefBase::make<Equalizer>();
+    } else {
+        LOG(ERROR) << __func__ << ": UUID "
+                   << " not supported";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Factory::destroyEffect(
+        const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_handle) {
+    if (in_handle) {
+        // TODO: b/245393900 need check the instance state with IEffect.getState before destroy.
+        return ndk::ScopedAStatus::ok();
+    } else {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+}
+
 }  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/EffectMain.cpp b/audio/aidl/default/EffectMain.cpp
index 3b3c39b..3219dd6 100644
--- a/audio/aidl/default/EffectMain.cpp
+++ b/audio/aidl/default/EffectMain.cpp
@@ -23,10 +23,11 @@
 int main() {
     // This is a debug implementation, always enable debug logging.
     android::base::SetMinimumLogSeverity(::android::base::DEBUG);
-    ABinderProcess_setThreadPoolMaxThreadCount(16);
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
 
     auto effectFactory =
             ndk::SharedRefBase::make<aidl::android::hardware::audio::effect::Factory>();
+
     std::string serviceName = std::string() + effectFactory->descriptor + "/default";
     binder_status_t status =
             AServiceManager_addService(effectFactory->asBinder().get(), serviceName.c_str());
diff --git a/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc b/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc
index 01c0e6e..68bbf5b 100644
--- a/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc
+++ b/audio/aidl/default/android.hardware.audio.effect.service-aidl.example.rc
@@ -2,7 +2,7 @@
     class hal
     user audioserver
     # media gid needed for /dev/fm (radio) and for /data/misc/media (tee)
-    group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub
+    group audio media
     capabilities BLOCK_SUSPEND
     ioprio rt 4
     task_profiles ProcessCapacityHigh HighPerformance
diff --git a/audio/aidl/default/equalizer/Android.bp b/audio/aidl/default/equalizer/Android.bp
new file mode 100644
index 0000000..b842149
--- /dev/null
+++ b/audio/aidl/default/equalizer/Android.bp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library_shared {
+    name: "libequalizer",
+    vendor: true,
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libstagefright_foundation",
+    ],
+    defaults: [
+        "latest_android_media_audio_common_types_ndk_shared",
+        "latest_android_hardware_audio_effect_ndk_shared",
+    ],
+    include_dirs: ["hardware/interfaces/audio/aidl/default/include/equalizer-impl"],
+    srcs: [
+        "Equalizer.cpp",
+    ],
+    visibility: [
+        "//hardware/interfaces/audio/aidl/default",
+    ],
+}
diff --git a/audio/aidl/default/equalizer/Equalizer.cpp b/audio/aidl/default/equalizer/Equalizer.cpp
new file mode 100644
index 0000000..8b157fa
--- /dev/null
+++ b/audio/aidl/default/equalizer/Equalizer.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AHAL_Equalizer"
+#include <android-base/logging.h>
+
+#include "Equalizer.h"
+
+namespace aidl::android::hardware::audio::effect {
+
+ndk::ScopedAStatus Equalizer::open() {
+    LOG(DEBUG) << __func__;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Equalizer::close() {
+    LOG(DEBUG) << __func__;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Equalizer::getDescriptor(Descriptor* _aidl_return) {
+    LOG(DEBUG) << __func__ << "descriptor " << mDesc.toString();
+    *_aidl_return = mDesc;
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/include/effectFactory-impl/EffectFactory.h b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h
index 7670250..8da5525 100644
--- a/audio/aidl/default/include/effectFactory-impl/EffectFactory.h
+++ b/audio/aidl/default/include/effectFactory-impl/EffectFactory.h
@@ -40,6 +40,28 @@
             const std::optional<::aidl::android::media::audio::common::AudioUuid>& in_instance,
             std::vector<Descriptor::Identity>* out_descriptor) override;
 
+    /**
+     * @brief Create an effect instance for a certain implementation (identified by UUID).
+     *
+     * @param in_impl_uuid Effect implementation UUID.
+     * @param _aidl_return A pointer to created effect instance.
+     * @return ndk::ScopedAStatus
+     */
+    ndk::ScopedAStatus createEffect(
+            const ::aidl::android::media::audio::common::AudioUuid& in_impl_uuid,
+            std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>* _aidl_return)
+            override;
+
+    /**
+     * @brief Destroy an effect instance.
+     *
+     * @param in_handle Effect instance handle.
+     * @return ndk::ScopedAStatus
+     */
+    ndk::ScopedAStatus destroyEffect(
+            const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_handle)
+            override;
+
   private:
     // List of effect descriptors supported by the devices.
     std::vector<Descriptor::Identity> mIdentityList;
diff --git a/audio/aidl/default/include/equalizer-impl/Equalizer.h b/audio/aidl/default/include/equalizer-impl/Equalizer.h
index 86f8c3a..ea16cb9 100644
--- a/audio/aidl/default/include/equalizer-impl/Equalizer.h
+++ b/audio/aidl/default/include/equalizer-impl/Equalizer.h
@@ -16,16 +16,36 @@
 
 #pragma once
 
+#include <aidl/android/hardware/audio/effect/BnEffect.h>
 #include <cstdlib>
 
 namespace aidl::android::hardware::audio::effect {
 
+// Equalizer type UUID.
+static const ::aidl::android::media::audio::common::AudioUuid EqualizerTypeUUID = {
+        static_cast<int32_t>(0x0bed4300),
+        0xddd6,
+        0x11db,
+        0x8f34,
+        {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
+
 // Equalizer implementation UUID.
-static const ::aidl::android::media::audio::common::AudioUuid EqualizerUUID = {
-        static_cast<int32_t>(0xce772f20),
+static const ::aidl::android::media::audio::common::AudioUuid EqualizerSwImplUUID = {
+        static_cast<int32_t>(0x0bed4300),
         0x847d,
         0x11df,
         0xbb17,
         {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}};
 
-}  // namespace aidl::android::hardware::audio::effect
\ No newline at end of file
+class Equalizer : public BnEffect {
+  public:
+    Equalizer() = default;
+    ndk::ScopedAStatus open() override;
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus getDescriptor(Descriptor* _aidl_return) override;
+
+  private:
+    // Effect descriptor.
+    Descriptor mDesc = {.common = {.id = {.type = EqualizerTypeUUID, .uuid = EqualizerSwImplUUID}}};
+};
+}  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
index f70948c..8b5eb13 100644
--- a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
 
 #define LOG_TAG "VtsHalAudioEffect"
 
@@ -29,43 +33,88 @@
 #include <aidl/android/hardware/audio/effect/IFactory.h>
 
 #include "AudioHalBinderServiceUtil.h"
+#include "TestUtils.h"
 
 using namespace android;
 
 using ndk::ScopedAStatus;
 
 using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::media::audio::common::AudioUuid;
 
-namespace ndk {
-std::ostream& operator<<(std::ostream& str, const ScopedAStatus& status) {
-    str << status.getDescription();
-    return str;
-}
-}  // namespace ndk
-
-class EffectFactory : public testing::TestWithParam<std::string> {
+class EffectFactoryHelper {
   public:
-    void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); }
+    explicit EffectFactoryHelper(const std::string& name) : mServiceName(name) {}
 
-    void TearDown() override {}
-
-    void ConnectToService() {
-        serviceName = GetParam();
-        factory = IFactory::fromBinder(binderUtil.connectToService(serviceName));
-        ASSERT_NE(factory, nullptr);
+    void ConnectToFactoryService() {
+        mEffectFactory = IFactory::fromBinder(binderUtil.connectToService(mServiceName));
+        ASSERT_NE(mEffectFactory, nullptr);
     }
 
-    void RestartService() {
-        ASSERT_NE(factory, nullptr);
-        factory = IFactory::fromBinder(binderUtil.restartService());
-        ASSERT_NE(factory, nullptr);
+    void RestartFactoryService() {
+        ASSERT_NE(mEffectFactory, nullptr);
+        mEffectFactory = IFactory::fromBinder(binderUtil.restartService());
+        ASSERT_NE(mEffectFactory, nullptr);
     }
 
-    std::shared_ptr<IFactory> factory;
-    std::string serviceName;
+    void QueryAllEffects() {
+        EXPECT_NE(mEffectFactory, nullptr);
+        EXPECT_IS_OK(mEffectFactory->queryEffects(std::nullopt, std::nullopt, &mCompleteIds));
+    }
+
+    void QueryEffects(const std::optional<AudioUuid>& in_type,
+                      const std::optional<AudioUuid>& in_instance,
+                      std::vector<Descriptor::Identity>* _aidl_return) {
+        EXPECT_NE(mEffectFactory, nullptr);
+        EXPECT_IS_OK(mEffectFactory->queryEffects(in_type, in_instance, _aidl_return));
+        mIds = *_aidl_return;
+    }
+
+    void CreateEffects() {
+        EXPECT_NE(mEffectFactory, nullptr);
+        for (const auto& id : mIds) {
+            std::shared_ptr<IEffect> effect;
+            EXPECT_IS_OK(mEffectFactory->createEffect(id.uuid, &effect));
+            EXPECT_NE(effect, nullptr) << id.toString();
+            mEffectIdMap[effect] = id;
+        }
+    }
+
+    void DestroyEffects() {
+        EXPECT_NE(mEffectFactory, nullptr);
+        for (const auto& it : mEffectIdMap) {
+            EXPECT_IS_OK(mEffectFactory->destroyEffect(it.first));
+        }
+        mEffectIdMap.clear();
+    }
+
+    std::shared_ptr<IFactory> GetFactory() { return mEffectFactory; }
+    const std::vector<Descriptor::Identity>& GetEffectIds() { return mIds; }
+    const std::vector<Descriptor::Identity>& GetCompleteEffectIdList() { return mCompleteIds; }
+    const std::unordered_map<std::shared_ptr<IEffect>, Descriptor::Identity>& GetEffectMap() {
+        return mEffectIdMap;
+    }
+
+  private:
+    std::shared_ptr<IFactory> mEffectFactory;
+    std::string mServiceName;
     AudioHalBinderServiceUtil binderUtil;
+    std::vector<Descriptor::Identity> mIds;
+    std::vector<Descriptor::Identity> mCompleteIds;
+    std::unordered_map<std::shared_ptr<IEffect>, Descriptor::Identity> mEffectIdMap;
+};
+
+/// Effect factory testing.
+class EffectFactoryTest : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override { ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService()); }
+
+    void TearDown() override { mFactory.DestroyEffects(); }
+
+    EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam());
+
     // TODO: these UUID can get from config file
     // ec7178ec-e5e1-4432-a3f4-4657e6795210
     const AudioUuid nullUuid = {static_cast<int32_t>(0xec7178ec),
@@ -77,25 +126,23 @@
             static_cast<int32_t>(0x0), 0x0, 0x0, 0x0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
 };
 
-TEST_P(EffectFactory, SetupAndTearDown) {
+TEST_P(EffectFactoryTest, SetupAndTearDown) {
     // Intentionally empty test body.
 }
 
-TEST_P(EffectFactory, CanBeRestarted) {
-    ASSERT_NO_FATAL_FAILURE(RestartService());
+TEST_P(EffectFactoryTest, CanBeRestarted) {
+    ASSERT_NO_FATAL_FAILURE(mFactory.RestartFactoryService());
 }
 
-TEST_P(EffectFactory, QueriedDescriptorList) {
+TEST_P(EffectFactoryTest, QueriedDescriptorList) {
     std::vector<Descriptor::Identity> descriptors;
-    ScopedAStatus status = factory->queryEffects(std::nullopt, std::nullopt, &descriptors);
-    EXPECT_EQ(EX_NONE, status.getExceptionCode());
-    EXPECT_NE(static_cast<int>(descriptors.size()), 0);
+    mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+    EXPECT_NE(descriptors.size(), 0UL);
 }
 
-TEST_P(EffectFactory, DescriptorUUIDNotNull) {
+TEST_P(EffectFactoryTest, DescriptorUUIDNotNull) {
     std::vector<Descriptor::Identity> descriptors;
-    ScopedAStatus status = factory->queryEffects(std::nullopt, std::nullopt, &descriptors);
-    EXPECT_EQ(EX_NONE, status.getExceptionCode());
+    mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
     // TODO: Factory eventually need to return the full list of MUST supported AOSP effects.
     for (auto& desc : descriptors) {
         EXPECT_NE(desc.type, zeroUuid);
@@ -103,28 +150,167 @@
     }
 }
 
-TEST_P(EffectFactory, QueriedDescriptorNotExistType) {
+TEST_P(EffectFactoryTest, QueriedDescriptorNotExistType) {
     std::vector<Descriptor::Identity> descriptors;
-    ScopedAStatus status = factory->queryEffects(nullUuid, std::nullopt, &descriptors);
-    EXPECT_EQ(EX_NONE, status.getExceptionCode());
-    EXPECT_EQ(static_cast<int>(descriptors.size()), 0);
+    mFactory.QueryEffects(nullUuid, std::nullopt, &descriptors);
+    EXPECT_EQ(descriptors.size(), 0UL);
 }
 
-TEST_P(EffectFactory, QueriedDescriptorNotExistInstance) {
+TEST_P(EffectFactoryTest, QueriedDescriptorNotExistInstance) {
     std::vector<Descriptor::Identity> descriptors;
-    ScopedAStatus status = factory->queryEffects(std::nullopt, nullUuid, &descriptors);
-    EXPECT_EQ(EX_NONE, status.getExceptionCode());
-    EXPECT_EQ(static_cast<int>(descriptors.size()), 0);
+    mFactory.QueryEffects(std::nullopt, nullUuid, &descriptors);
+    EXPECT_EQ(descriptors.size(), 0UL);
 }
 
-INSTANTIATE_TEST_SUITE_P(EffectFactoryTest, EffectFactory,
+TEST_P(EffectFactoryTest, CreateAndDestroyRepeat) {
+    std::vector<Descriptor::Identity> descriptors;
+    mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+    auto numIds = mFactory.GetEffectIds().size();
+    EXPECT_NE(numIds, 0UL);
+
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+    mFactory.CreateEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+    mFactory.DestroyEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+
+    // Create and destroy again
+    mFactory.CreateEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+    mFactory.DestroyEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+}
+
+TEST_P(EffectFactoryTest, CreateMultipleInstanceOfSameEffect) {
+    std::vector<Descriptor::Identity> descriptors;
+    mFactory.QueryEffects(std::nullopt, std::nullopt, &descriptors);
+    auto numIds = mFactory.GetEffectIds().size();
+    EXPECT_NE(numIds, 0UL);
+
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+    mFactory.CreateEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), numIds);
+    // Create effect instances of same implementation
+    mFactory.CreateEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 2 * numIds);
+
+    mFactory.CreateEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 3 * numIds);
+
+    mFactory.DestroyEffects();
+    EXPECT_EQ(mFactory.GetEffectMap().size(), 0UL);
+}
+
+INSTANTIATE_TEST_SUITE_P(EffectFactoryTest, EffectFactoryTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)),
                          android::PrintInstanceNameToString);
-GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EffectFactory);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EffectFactoryTest);
+
+/// Effect testing.
+class AudioEffect : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(mFactory.ConnectToFactoryService());
+        ASSERT_NO_FATAL_FAILURE(mFactory.CreateEffects());
+    }
+
+    void TearDown() override {
+        CloseEffects();
+        ASSERT_NO_FATAL_FAILURE(mFactory.DestroyEffects());
+    }
+
+    void OpenEffects() {
+        auto open = [](const std::shared_ptr<IEffect>& effect) { EXPECT_IS_OK(effect->open()); };
+        EXPECT_NO_FATAL_FAILURE(ForEachEffect(open));
+    }
+
+    void CloseEffects() {
+        auto close = [](const std::shared_ptr<IEffect>& effect) { EXPECT_IS_OK(effect->close()); };
+        EXPECT_NO_FATAL_FAILURE(ForEachEffect(close));
+    }
+
+    void GetEffectDescriptors() {
+        auto get = [](const std::shared_ptr<IEffect>& effect) {
+            Descriptor desc;
+            EXPECT_IS_OK(effect->getDescriptor(&desc));
+        };
+        EXPECT_NO_FATAL_FAILURE(ForEachEffect(get));
+    }
+
+    template <typename Functor>
+    void ForEachEffect(Functor functor) {
+        auto effectMap = mFactory.GetEffectMap();
+        for (const auto& it : effectMap) {
+            SCOPED_TRACE(it.second.toString());
+            functor(it.first);
+        }
+    }
+
+    EffectFactoryHelper mFactory = EffectFactoryHelper(GetParam());
+};
+
+TEST_P(AudioEffect, OpenEffectTest) {
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+}
+
+TEST_P(AudioEffect, OpenAndCloseEffect) {
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, CloseUnopenedEffectTest) {
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, DoubleOpenCloseEffects) {
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+
+    EXPECT_NO_FATAL_FAILURE(OpenEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+    EXPECT_NO_FATAL_FAILURE(CloseEffects());
+}
+
+TEST_P(AudioEffect, GetDescriptors) {
+    EXPECT_NO_FATAL_FAILURE(GetEffectDescriptors());
+}
+
+TEST_P(AudioEffect, DescriptorIdExistAndUnique) {
+    auto checker = [&](const std::shared_ptr<IEffect>& effect) {
+        Descriptor desc;
+        std::vector<Descriptor::Identity> idList;
+        EXPECT_IS_OK(effect->getDescriptor(&desc));
+        mFactory.QueryEffects(desc.common.id.type, desc.common.id.uuid, &idList);
+        EXPECT_EQ(idList.size(), 1UL);
+    };
+    EXPECT_NO_FATAL_FAILURE(ForEachEffect(checker));
+
+    // Check unique with a set
+    auto stringHash = [](const Descriptor::Identity& id) {
+        return std::hash<std::string>()(id.toString());
+    };
+    auto vec = mFactory.GetCompleteEffectIdList();
+    std::unordered_set<Descriptor::Identity, decltype(stringHash)> idSet(0, stringHash);
+    for (auto it : vec) {
+        EXPECT_EQ(idSet.count(it), 0UL);
+        idSet.insert(it);
+    }
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioEffectTest, AudioEffect,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IFactory::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioEffect);
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
-}
+}
\ No newline at end of file
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index c295aaa..93688fc 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -17,7 +17,6 @@
 #include <array>
 #include <string>
 
-#include <android-base/test_utils.h>
 #include <gtest/gtest.h>
 
 #define LOG_TAG "HidlUtils_Test"
@@ -1125,12 +1124,15 @@
 TYPED_TEST_SUITE(FilterTest, FilterTestTypeParams);
 
 TYPED_TEST(FilterTest, FilterOutNonVendorTags) {
-    SKIP_WITH_HWASAN; // b/230535046
     TypeParam emptyTags;
     EXPECT_EQ(emptyTags, HidlUtils::filterOutNonVendorTags(emptyTags));
 
-    TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
-    EXPECT_EQ(allVendorTags, HidlUtils::filterOutNonVendorTags(allVendorTags));
+    // b/248421569, allocate two vendor tags at a time can run out of memory
+    // TypeParam allVendorTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+    TypeParam allVendorTags1 = {{"VX_GOOGLE_VR_42"}};
+    EXPECT_EQ(allVendorTags1, HidlUtils::filterOutNonVendorTags(allVendorTags1));
+    TypeParam allVendorTags2 = {{"VX_GOOGLE_1E100"}};
+    EXPECT_EQ(allVendorTags2, HidlUtils::filterOutNonVendorTags(allVendorTags2));
 
     TypeParam oneVendorTag = {{"", "VX_GOOGLE_VR", "random_string"}};
     TypeParam oneVendorTagOnly = HidlUtils::filterOutNonVendorTags(oneVendorTag);
diff --git a/audio/policy/1.0/xml/api/current.txt b/audio/policy/1.0/xml/api/current.txt
index 0b77d45..84a2b71 100644
--- a/audio/policy/1.0/xml/api/current.txt
+++ b/audio/policy/1.0/xml/api/current.txt
@@ -217,6 +217,10 @@
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_GAME;
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_MEDIA;
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION;
+    enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
+    enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
+    enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
+    enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_EVENT;
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE;
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_UNKNOWN;
     enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_VIRTUAL_SOURCE;
diff --git a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
index 3ce12e7..b58a6c8 100644
--- a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
+++ b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd
@@ -347,6 +347,11 @@
             <xs:enumeration value="AUDIO_USAGE_ALARM"/>
             <xs:enumeration value="AUDIO_USAGE_NOTIFICATION"/>
             <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE"/>
+            <!-- Note: the following 3 values were deprecated in Android T (13) SDK -->
+            <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST"/>
+            <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT"/>
+            <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED"/>
+            <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_EVENT"/>
             <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY"/>
             <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/>
             <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/>
diff --git a/automotive/remoteaccess/Android.bp b/automotive/remoteaccess/Android.bp
new file mode 100644
index 0000000..ac04354
--- /dev/null
+++ b/automotive/remoteaccess/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+aidl_interface {
+    name: "android.hardware.automotive.remoteaccess",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/automotive/remoteaccess/**/*.aidl",
+    ],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            sdk_version: "module_current",
+            min_sdk_version: "31",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.car.framework",
+            ],
+        },
+    },
+}
diff --git a/automotive/remoteaccess/OWNERS b/automotive/remoteaccess/OWNERS
new file mode 100644
index 0000000..d6969e5
--- /dev/null
+++ b/automotive/remoteaccess/OWNERS
@@ -0,0 +1,2 @@
+ericjeong@google.com
+shanyu@google.com
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl
index 918ae3a..da4f1d4 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/ApState.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.automotive.remoteaccess;
+@VintfStability
+parcelable ApState {
+  boolean isReadyForRemoteTask;
+  boolean isWakeupRequired;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl
similarity index 82%
copy from radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
copy to automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl
index 930ff54..9b6eb2f 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl
@@ -31,11 +31,12 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.network;
+package android.hardware.automotive.remoteaccess;
 @VintfStability
-parcelable NrRegistrationInfo {
-  android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
-  int extraInfo;
-  const int EXTRA_SMS_OVER_NAS_ALLOWED = 1;
-  const int EXTRA_REGISTERED_FOR_EMERGENCY = 2;
+interface IRemoteAccess {
+  String getDeviceId();
+  String getWakeupServiceName();
+  void setRemoteTaskCallback(android.hardware.automotive.remoteaccess.IRemoteTaskCallback callback);
+  void clearRemoteTaskCallback();
+  void notifyApStateChange(in android.hardware.automotive.remoteaccess.ApState state);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl
index 918ae3a..295100e 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/automotive/remoteaccess/aidl_api/android.hardware.automotive.remoteaccess/current/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,8 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.automotive.remoteaccess;
+@VintfStability
+interface IRemoteTaskCallback {
+  oneway void onRemoteTaskRequested(String clientId, in byte[] data);
 }
diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl
new file mode 100644
index 0000000..c8eb3ef
--- /dev/null
+++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/ApState.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.automotive.remoteaccess;
+
+@VintfStability
+parcelable ApState {
+    /**
+     * Whether AP (application processor) is ready to receive remote tasks.
+     *
+     * If this is true. AP is powered on and the car service is ready to handle
+     * remote tasks.
+     */
+    boolean isReadyForRemoteTask;
+    /**
+     * Whether AP (application processor) needs to be woken up.
+     *
+     * While the AP is shutting down, this will be set to false to prevent the
+     * wakeup signal to interrupt the shutdown process. At the last step of the
+     * shutdown process, this will be set to true so that AP will be waken
+     * up when task arrives. After AP starts up, this will be set to false
+     * to prevent unnecessary wakeup signal.
+     */
+    boolean isWakeupRequired;
+}
diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl
new file mode 100644
index 0000000..a198b03
--- /dev/null
+++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteAccess.aidl
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.automotive.remoteaccess;
+
+import android.hardware.automotive.remoteaccess.ApState;
+import android.hardware.automotive.remoteaccess.IRemoteTaskCallback;
+
+/**
+ * Interface representing a remote wakeup client.
+ *
+ * A wakeup client is a binary outside Android framework that communicates with
+ * a wakeup server and receives wake up command.
+ */
+@VintfStability
+interface IRemoteAccess {
+    /**
+     * Gets a unique device ID that could be recognized by wake up server.
+     *
+     * This device ID is provisioned during car production and is registered
+     * with the wake up server.
+     *
+     * @return a unique device ID.
+     */
+    String getDeviceId();
+
+    /**
+     * Gets the name for the remote wakeup server.
+     *
+     * This name will be provided to remote task server during registration
+     * and used by remote task server to find the remote wakeup server to
+     * use for waking up the device. This name must be pre-negotiated between
+     * the remote wakeup server/client and the remote task server/client and
+     * must be unique. We recommend the format to be a human readable string
+     * with reverse domain name notation (reverse-DNS), e.g.
+     * "com.google.vehicle.wakeup".
+     */
+    String getWakeupServiceName();
+
+    /**
+     * Sets a callback to be called when a remote task is requested.
+     *
+     * @param callback A callback to be called when a remote task is requested.
+     */
+    void setRemoteTaskCallback(IRemoteTaskCallback callback);
+
+    /**
+     * Clears a previously set remote task callback.
+     *
+     * If no callback was set, this operation is no-op.
+     */
+    void clearRemoteTaskCallback();
+
+    /**
+     * Notifies whether AP is ready to receive remote tasks.
+     *
+     * <p>Wakeup client should store and use this state until a new call with a
+     * different state arrives.
+     *
+     * <p>If {@code isReadyForRemoteTask} is true, the wakeup client may send
+     * the task received from the server to AP immediately.
+     *
+     * <p>If {@code isReadyForRemoteTask} is false, it must store the received
+     * remote tasks and wait until AP is ready to receive tasks. If it takes too
+     * long for AP to become ready, the task must be reported to remote task
+     * server as failed. Implementation must make sure no duplicate tasks are
+     * delivered to AP.
+     *
+     * <p>If {@code isWakeupRequired} is true, it must try to wake up AP when a
+     * remote task arrives or when there are pending requests.
+     *
+     * <p>If {@code isWakeupRequired} is false, it must not try to wake up AP.
+     */
+    void notifyApStateChange(in ApState state);
+}
diff --git a/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl
new file mode 100644
index 0000000..7a1616f
--- /dev/null
+++ b/automotive/remoteaccess/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.automotive.remoteaccess;
+
+/**
+ * The callback interface for car service to receive tasks from wakup client.
+ */
+@VintfStability
+interface IRemoteTaskCallback {
+    /**
+     * A callback that is called when a remote task is requested.
+     *
+     * @param clientId An ID to uniquely identify a remote task client.
+     * @param data Opaque task data passed to the remote task client.
+     */
+    oneway void onRemoteTaskRequested(String clientId, in byte[] data);
+}
diff --git a/automotive/remoteaccess/impl/default/client/Android.bp b/automotive/remoteaccess/impl/default/client/Android.bp
new file mode 100644
index 0000000..6327637
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/Android.bp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.hardware.automotive.remoteaccess@V1-default-service",
+    vendor: true,
+    vintf_fragments: ["remoteaccess-default-service.xml"],
+    init_rc: ["remoteaccess-default-service.rc"],
+    relative_install_path: "hw",
+    srcs: ["src/RemoteAccessImpl.cpp"],
+    whole_static_libs: [
+        "RemoteAccessService",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "liblog",
+        "libutils",
+        "libgrpc++",
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
+
+cc_library {
+    name: "RemoteAccessService",
+    vendor: true,
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    srcs: [
+        "src/RemoteAccessService.cpp",
+    ],
+    whole_static_libs: [
+        "android.hardware.automotive.remoteaccess-V1-ndk",
+        "wakeup_client_protos",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "liblog",
+        "libutils",
+        "libgrpc++",
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/automotive/remoteaccess/impl/default/client/include/RemoteAccessService.h b/automotive/remoteaccess/impl/default/client/include/RemoteAccessService.h
new file mode 100644
index 0000000..806440a
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/include/RemoteAccessService.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteAccess.h>
+#include <aidl/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.h>
+#include <android-base/thread_annotations.h>
+#include <wakeup_client.grpc.pb.h>
+
+#include <string>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+class RemoteAccessService
+    : public aidl::android::hardware::automotive::remoteaccess::BnRemoteAccess {
+  public:
+    explicit RemoteAccessService(WakeupClient::StubInterface* grpcStub);
+
+    ~RemoteAccessService();
+
+    ndk::ScopedAStatus getDeviceId(std::string* deviceId) override;
+
+    ndk::ScopedAStatus getWakeupServiceName(std::string* wakeupServiceName) override;
+
+    ndk::ScopedAStatus setRemoteTaskCallback(
+            const std::shared_ptr<
+                    aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback>&
+                    callback) override;
+
+    ndk::ScopedAStatus clearRemoteTaskCallback() override;
+
+    ndk::ScopedAStatus notifyApStateChange(
+            const aidl::android::hardware::automotive::remoteaccess::ApState& newState) override;
+
+  private:
+    // For testing.
+    friend class RemoteAccessServiceUnitTest;
+
+    WakeupClient::StubInterface* mGrpcStub;
+    std::thread mThread;
+    std::mutex mLock;
+    std::condition_variable mCv;
+    std::shared_ptr<aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback>
+            mRemoteTaskCallback GUARDED_BY(mLock);
+    std::unique_ptr<grpc::ClientContext> mGetRemoteTasksContext GUARDED_BY(mLock);
+    // Associated with mCv to notify the task loop to stop waiting and exit.
+    bool mTaskWaitStopped GUARDED_BY(mLock);
+    // A mutex to make sure startTaskLoop does not overlap with stopTaskLoop.
+    std::mutex mStartStopTaskLoopLock;
+    bool mTaskLoopRunning GUARDED_BY(mStartStopTaskLoopLock);
+    // Default wait time before retry connecting to remote access client is 10s.
+    size_t mRetryWaitInMs = 10'000;
+
+    void runTaskLoop();
+    void maybeStartTaskLoop();
+    void maybeStopTaskLoop();
+
+    void setRetryWaitInMs(size_t retryWaitInMs) { mRetryWaitInMs = retryWaitInMs; }
+};
+
+}  // namespace remoteaccess
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.rc b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.rc
new file mode 100644
index 0000000..b7a9cdc
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.rc
@@ -0,0 +1,4 @@
+service vendor.remoteaccess-default /vendor/bin/hw/android.hardware.automotive.remoteaccess@V1-default-service
+    class hal
+    user vehicle_network
+    group system inet
diff --git a/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.xml b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.xml
new file mode 100644
index 0000000..d050a1b
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.automotive.remoteaccess</name>
+        <version>1</version>
+        <fqname>IRemoteAccess/default</fqname>
+    </hal>
+</manifest>
diff --git a/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp b/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp
new file mode 100644
index 0000000..7431898
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "RemoteAccessImpl"
+
+#include "RemoteAccessService.h"
+
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <utils/Log.h>
+
+int main(int /* argc */, char* /* argv */[]) {
+    ALOGI("Registering RemoteAccessService as service...");
+
+    // TODO(b/241483300): Create GrpcClientStub here.
+    auto service = ndk::SharedRefBase::make<
+            android::hardware::automotive::remoteaccess::RemoteAccessService>(nullptr);
+
+    binder_exception_t err = AServiceManager_addService(
+            service->asBinder().get(), "android.hardware.automotive.remote.IRemoteAccess/default");
+    if (err != EX_NONE) {
+        ALOGE("failed to register android.hardware.automotive.remote.IRemoteAccess service, "
+              "exception: %d",
+              err);
+        return 1;
+    }
+
+    if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) {
+        ALOGE("%s", "failed to set thread pool max thread count");
+        return 1;
+    }
+    ABinderProcess_startThreadPool();
+
+    ALOGI("RemoteAccess service Ready");
+
+    ABinderProcess_joinThreadPool();
+
+    ALOGW("Should not reach here");
+
+    return 0;
+}
diff --git a/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp b/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp
new file mode 100644
index 0000000..6b97840
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RemoteAccessService.h"
+
+#include <android/binder_status.h>
+#include <grpc++/grpc++.h>
+#include <utils/Log.h>
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+namespace {
+
+using ::aidl::android::hardware::automotive::remoteaccess::ApState;
+using ::aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback;
+using ::android::base::ScopedLockAssertion;
+using ::grpc::ClientContext;
+using ::grpc::ClientReaderInterface;
+using ::grpc::Status;
+using ::grpc::StatusCode;
+using ::ndk::ScopedAStatus;
+
+const std::string WAKEUP_SERVICE_NAME = "com.google.vehicle.wakeup";
+
+std::vector<uint8_t> stringToBytes(const std::string& s) {
+    const char* data = s.data();
+    return std::vector<uint8_t>(data, data + s.size());
+}
+
+ScopedAStatus rpcStatusToScopedAStatus(const Status& status, const std::string& errorMsg) {
+    return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            status.error_code(), (errorMsg + ", error: " + status.error_message()).c_str());
+}
+
+}  // namespace
+
+RemoteAccessService::RemoteAccessService(WakeupClient::StubInterface* grpcStub)
+    : mGrpcStub(grpcStub){};
+
+RemoteAccessService::~RemoteAccessService() {
+    maybeStopTaskLoop();
+}
+
+void RemoteAccessService::maybeStartTaskLoop() {
+    std::lock_guard<std::mutex> lockGuard(mStartStopTaskLoopLock);
+    if (mTaskLoopRunning) {
+        return;
+    }
+
+    mThread = std::thread([this]() { runTaskLoop(); });
+
+    mTaskLoopRunning = true;
+}
+
+void RemoteAccessService::maybeStopTaskLoop() {
+    std::lock_guard<std::mutex> lockGuard(mStartStopTaskLoopLock);
+    if (!mTaskLoopRunning) {
+        return;
+    }
+
+    {
+        std::lock_guard<std::mutex> lockGuard(mLock);
+        // Try to stop the reading stream.
+        if (mGetRemoteTasksContext) {
+            mGetRemoteTasksContext->TryCancel();
+            mGetRemoteTasksContext.reset();
+        }
+        mTaskWaitStopped = true;
+        mCv.notify_all();
+    }
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+
+    mTaskLoopRunning = false;
+}
+
+void RemoteAccessService::runTaskLoop() {
+    GetRemoteTasksRequest request = {};
+    std::unique_ptr<ClientReaderInterface<GetRemoteTasksResponse>> reader;
+    while (true) {
+        {
+            std::lock_guard<std::mutex> lockGuard(mLock);
+            mGetRemoteTasksContext.reset(new ClientContext());
+            reader = mGrpcStub->GetRemoteTasks(mGetRemoteTasksContext.get(), request);
+        }
+        GetRemoteTasksResponse response;
+        while (reader->Read(&response)) {
+            std::shared_ptr<IRemoteTaskCallback> callback;
+            {
+                std::lock_guard<std::mutex> lockGuard(mLock);
+                callback = mRemoteTaskCallback;
+            }
+            if (callback == nullptr) {
+                continue;
+            }
+            ScopedAStatus callbackStatus = callback->onRemoteTaskRequested(
+                    response.clientid(), stringToBytes(response.data()));
+            if (!callbackStatus.isOk()) {
+                ALOGE("Failed to call onRemoteTaskRequested callback, status: %d, message: %s",
+                      callbackStatus.getStatus(), callbackStatus.getMessage());
+            }
+        }
+        Status status = reader->Finish();
+
+        ALOGE("GetRemoteTasks stream breaks, code: %d, message: %s, sleeping for 10s and retry",
+              status.error_code(), status.error_message().c_str());
+        // The long lasting connection should not return. But if the server returns, retry after
+        // 10s.
+        {
+            std::unique_lock lk(mLock);
+            if (mCv.wait_for(lk, std::chrono::milliseconds(mRetryWaitInMs), [this] {
+                    ScopedLockAssertion lockAssertion(mLock);
+                    return mTaskWaitStopped;
+                })) {
+                // If the stopped flag is set, we are quitting, exit the loop.
+                break;
+            }
+        }
+    }
+}
+
+ScopedAStatus RemoteAccessService::getDeviceId(std::string* deviceId) {
+    // TODO(b/241483300): Call VHAL to get VIN.
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::getWakeupServiceName(std::string* wakeupServiceName) {
+    *wakeupServiceName = WAKEUP_SERVICE_NAME;
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::setRemoteTaskCallback(
+        [[maybe_unused]] const std::shared_ptr<IRemoteTaskCallback>& callback) {
+    std::lock_guard<std::mutex> lockGuard(mLock);
+    mRemoteTaskCallback = callback;
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::clearRemoteTaskCallback() {
+    std::lock_guard<std::mutex> lockGuard(mLock);
+    mRemoteTaskCallback.reset();
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::notifyApStateChange(const ApState& newState) {
+    ClientContext context;
+    NotifyWakeupRequiredRequest request = {};
+    request.set_iswakeuprequired(newState.isWakeupRequired);
+    NotifyWakeupRequiredResponse response = {};
+    Status status = mGrpcStub->NotifyWakeupRequired(&context, request, &response);
+    if (!status.ok()) {
+        return rpcStatusToScopedAStatus(status, "Failed to notify isWakeupRequired");
+    }
+
+    if (newState.isReadyForRemoteTask) {
+        maybeStartTaskLoop();
+    } else {
+        maybeStopTaskLoop();
+    }
+    return ScopedAStatus::ok();
+}
+
+}  // namespace remoteaccess
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/remoteaccess/impl/default/proto/Android.bp b/automotive/remoteaccess/impl/default/proto/Android.bp
new file mode 100644
index 0000000..31b9d0e
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/proto/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+genrule {
+    name: "wakeup_client_pb_h",
+    tools: [
+        "aprotoc",
+        "protoc-gen-grpc-cpp-plugin",
+    ],
+    cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+    srcs: [
+        "wakeup_client.proto",
+    ],
+    out: [
+        "wakeup_client.pb.h",
+        "wakeup_client.grpc.pb.h",
+    ],
+    vendor: true,
+}
+
+genrule {
+    name: "wakeup_client_pb_cc",
+    tools: [
+        "aprotoc",
+        "protoc-gen-grpc-cpp-plugin",
+    ],
+    cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+    srcs: [
+        "wakeup_client.proto",
+    ],
+    out: [
+        "wakeup_client.pb.cc",
+        "wakeup_client.grpc.pb.cc",
+    ],
+    vendor: true,
+}
+
+cc_library_static {
+    name: "wakeup_client_protos",
+    vendor: true,
+    host_supported: true,
+    include_dirs: [
+        "external/protobuf/src",
+    ],
+    generated_headers: [
+        "wakeup_client_pb_h",
+    ],
+    export_generated_headers: [
+        "wakeup_client_pb_h",
+    ],
+    generated_sources: [
+        "wakeup_client_pb_cc",
+    ],
+    shared_libs: [
+        "libgrpc++",
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/automotive/remoteaccess/impl/default/proto/wakeup_client.proto b/automotive/remoteaccess/impl/default/proto/wakeup_client.proto
new file mode 100644
index 0000000..4fe0d01
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/proto/wakeup_client.proto
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package android.hardware.automotive.remoteaccess;
+
+/**
+ * Service provided by a wakeup client running on TCU.
+ */
+service WakeupClient {
+    /**
+     * Establish a long-live connection to receive remote tasks.
+     *
+     * <p>For the server, whenever a remote task arrives, if the connection is
+     * alive, it will use the return stream to return a task's information.
+     *
+     * <p>If the connection is not alive, the server must stores the remote task
+     * until a new connection is established (which means AP is ready to
+     * receive remote task again) and send the stored tasks.
+     *
+     * <p>If the server closes the connection, the client will try to
+     * reestablish the connection.
+     */
+    rpc GetRemoteTasks(GetRemoteTasksRequest) returns (stream GetRemoteTasksResponse) {}
+
+    /**
+     * Notifies whether AP is required to be waken up when remote task arrives.
+     *
+     * <p>Wakeup client should store and use this state until a new call with a
+     * different state arrives.
+     *
+     * <p>If {@code isWakeupRequired} in the request is true, it must wake up AP
+     * when a remote task arrives.
+     *
+     * <p>If {@code isWakeupRequired} in the request is false, it must not try
+     * to wake up AP.
+     */
+    rpc NotifyWakeupRequired(NotifyWakeupRequiredRequest) returns (NotifyWakeupRequiredResponse) {}
+}
+
+message GetRemoteTasksRequest {}
+
+message GetRemoteTasksResponse {
+    string clientId = 1;
+    bytes data = 2;
+}
+
+message NotifyWakeupRequiredRequest {
+    bool isWakeupRequired = 1;
+}
+
+message NotifyWakeupRequiredResponse {}
diff --git a/automotive/remoteaccess/impl/default/test/Android.bp b/automotive/remoteaccess/impl/default/test/Android.bp
new file mode 100644
index 0000000..e92440f
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/test/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "RemoteAccessServiceUnitTest",
+    vendor: true,
+    srcs: ["*.cpp"],
+    whole_static_libs: [
+        "RemoteAccessService",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "liblog",
+        "libutils",
+        "libgrpc++",
+        "libprotobuf-cpp-full",
+    ],
+    // libgrpc++.so is installed as root, require root to access it.
+    require_root: true,
+    static_libs: [
+        "libgtest",
+        "libgmock",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/automotive/remoteaccess/impl/default/test/RemoteAccessServiceUnitTest.cpp b/automotive/remoteaccess/impl/default/test/RemoteAccessServiceUnitTest.cpp
new file mode 100644
index 0000000..11523f6
--- /dev/null
+++ b/automotive/remoteaccess/impl/default/test/RemoteAccessServiceUnitTest.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RemoteAccessService.h"
+
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteTaskCallback.h>
+#include <gmock/gmock.h>
+#include <grpcpp/test/mock_stream.h>
+#include <gtest/gtest.h>
+#include <wakeup_client.grpc.pb.h>
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+using ::android::base::ScopedLockAssertion;
+
+using ::aidl::android::hardware::automotive::remoteaccess::ApState;
+using ::aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback;
+
+using ::grpc::ClientAsyncReaderInterface;
+using ::grpc::ClientAsyncResponseReaderInterface;
+using ::grpc::ClientContext;
+using ::grpc::ClientReader;
+using ::grpc::ClientReaderInterface;
+using ::grpc::CompletionQueue;
+using ::grpc::Status;
+using ::grpc::testing::MockClientReader;
+using ::ndk::ScopedAStatus;
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+class MockGrpcClientStub : public WakeupClient::StubInterface {
+  public:
+    MOCK_METHOD(ClientReaderInterface<GetRemoteTasksResponse>*, GetRemoteTasksRaw,
+                (ClientContext * context, const GetRemoteTasksRequest& request));
+    MOCK_METHOD(Status, NotifyWakeupRequired,
+                (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+                 NotifyWakeupRequiredResponse* response));
+    // Async methods which we do not care.
+    MOCK_METHOD(ClientAsyncReaderInterface<GetRemoteTasksResponse>*, AsyncGetRemoteTasksRaw,
+                (ClientContext * context, const GetRemoteTasksRequest& request, CompletionQueue* cq,
+                 void* tag));
+    MOCK_METHOD(ClientAsyncReaderInterface<GetRemoteTasksResponse>*, PrepareAsyncGetRemoteTasksRaw,
+                (ClientContext * context, const GetRemoteTasksRequest& request,
+                 CompletionQueue* cq));
+    MOCK_METHOD(ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>*,
+                AsyncNotifyWakeupRequiredRaw,
+                (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+                 CompletionQueue* cq));
+    MOCK_METHOD(ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>*,
+                PrepareAsyncNotifyWakeupRequiredRaw,
+                (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+                 CompletionQueue* cq));
+};
+
+class FakeRemoteTaskCallback : public BnRemoteTaskCallback {
+  public:
+    ScopedAStatus onRemoteTaskRequested(const std::string& clientId,
+                                        const std::vector<uint8_t>& data) override {
+        std::lock_guard<std::mutex> lockGuard(mLock);
+        mDataByClientId[clientId] = data;
+        mTaskCount++;
+        mCv.notify_all();
+        return ScopedAStatus::ok();
+    }
+
+    std::vector<uint8_t> getData(const std::string& clientId) { return mDataByClientId[clientId]; }
+
+    bool wait(size_t taskCount, size_t timeoutInSec) {
+        std::unique_lock<std::mutex> lock(mLock);
+        return mCv.wait_for(lock, std::chrono::seconds(timeoutInSec), [taskCount, this] {
+            ScopedLockAssertion lockAssertion(mLock);
+            return mTaskCount >= taskCount;
+        });
+    }
+
+  private:
+    std::mutex mLock;
+    std::unordered_map<std::string, std::vector<uint8_t>> mDataByClientId GUARDED_BY(mLock);
+    size_t mTaskCount GUARDED_BY(mLock) = 0;
+    std::condition_variable mCv;
+};
+
+class RemoteAccessServiceUnitTest : public ::testing::Test {
+  public:
+    virtual void SetUp() override {
+        mGrpcWakeupClientStub = std::make_unique<MockGrpcClientStub>();
+        mService = ndk::SharedRefBase::make<RemoteAccessService>(mGrpcWakeupClientStub.get());
+    }
+
+    MockGrpcClientStub* getGrpcWakeupClientStub() { return mGrpcWakeupClientStub.get(); }
+
+    RemoteAccessService* getService() { return mService.get(); }
+
+    void setRetryWaitInMs(size_t retryWaitInMs) { mService->setRetryWaitInMs(retryWaitInMs); }
+
+  private:
+    std::unique_ptr<MockGrpcClientStub> mGrpcWakeupClientStub;
+    std::shared_ptr<RemoteAccessService> mService;
+    MockClientReader<GetRemoteTasksResponse>* mMockTaskReader;
+};
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetWakeupServiceName) {
+    std::string serviceName;
+
+    ScopedAStatus status = getService()->getWakeupServiceName(&serviceName);
+
+    EXPECT_TRUE(status.isOk());
+    EXPECT_EQ(serviceName, "com.google.vehicle.wakeup");
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestNotifyApStateChangeWakeupRequired) {
+    bool isWakeupRequired = false;
+    EXPECT_CALL(*getGrpcWakeupClientStub(), NotifyWakeupRequired)
+            .WillOnce([&isWakeupRequired]([[maybe_unused]] ClientContext* context,
+                                          const NotifyWakeupRequiredRequest& request,
+                                          [[maybe_unused]] NotifyWakeupRequiredResponse* response) {
+                isWakeupRequired = request.iswakeuprequired();
+                return Status();
+            });
+
+    ApState newState = {
+            .isWakeupRequired = true,
+    };
+    ScopedAStatus status = getService()->notifyApStateChange(newState);
+
+    EXPECT_TRUE(status.isOk());
+    EXPECT_TRUE(isWakeupRequired);
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasks) {
+    GetRemoteTasksResponse response1;
+    std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+    response1.set_clientid("1");
+    response1.set_data(testData.data(), testData.size());
+    GetRemoteTasksResponse response2;
+    response2.set_clientid("2");
+    std::shared_ptr<FakeRemoteTaskCallback> callback =
+            ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+    ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+            .WillByDefault(
+                    [response1, response2]([[maybe_unused]] ClientContext* context,
+                                           [[maybe_unused]] const GetRemoteTasksRequest& request) {
+                        // mockReader ownership will be transferred to the client so we don't own it
+                        // here.
+                        MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+                                new MockClientReader<GetRemoteTasksResponse>();
+                        EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+                        EXPECT_CALL(*mockClientReader, Read(_))
+                                .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true)))
+                                .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true)))
+                                .WillRepeatedly(Return(false));
+                        return mockClientReader;
+                    });
+
+    getService()->setRemoteTaskCallback(callback);
+    // Start the long live connection to receive tasks.
+    ApState newState = {
+            .isReadyForRemoteTask = true,
+    };
+    ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+    ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+            << "Did not receive enough tasks";
+    EXPECT_EQ(callback->getData("1"), testData);
+    EXPECT_EQ(callback->getData("2"), std::vector<uint8_t>());
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksRetryConnection) {
+    GetRemoteTasksResponse response;
+    std::shared_ptr<FakeRemoteTaskCallback> callback =
+            ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+    ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+            .WillByDefault([response]([[maybe_unused]] ClientContext* context,
+                                      [[maybe_unused]] const GetRemoteTasksRequest& request) {
+                // mockReader ownership will be transferred to the client so we don't own it here.
+                MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+                        new MockClientReader<GetRemoteTasksResponse>();
+                EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+                // Connection fails after receiving one task. Should retry after some time.
+                EXPECT_CALL(*mockClientReader, Read(_))
+                        .WillOnce(DoAll(SetArgPointee<0>(response), Return(true)))
+                        .WillRepeatedly(Return(false));
+                return mockClientReader;
+            });
+
+    getService()->setRemoteTaskCallback(callback);
+    setRetryWaitInMs(100);
+    // Start the long live connection to receive tasks.
+    ApState newState = {
+            .isReadyForRemoteTask = true,
+    };
+    ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+    ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+            << "Did not receive enough tasks";
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksDefaultNotReady) {
+    GetRemoteTasksResponse response1;
+    std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+    response1.set_clientid("1");
+    response1.set_data(testData.data(), testData.size());
+    GetRemoteTasksResponse response2;
+    response2.set_clientid("2");
+    std::shared_ptr<FakeRemoteTaskCallback> callback =
+            ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+    EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(0);
+
+    // Default state is not ready for remote tasks, so no callback will be called.
+    getService()->setRemoteTaskCallback(callback);
+
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksNotReadyAfterReady) {
+    GetRemoteTasksResponse response1;
+    std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+    response1.set_clientid("1");
+    response1.set_data(testData.data(), testData.size());
+    GetRemoteTasksResponse response2;
+    response2.set_clientid("2");
+    std::shared_ptr<FakeRemoteTaskCallback> callback =
+            ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+    ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+            .WillByDefault(
+                    [response1, response2]([[maybe_unused]] ClientContext* context,
+                                           [[maybe_unused]] const GetRemoteTasksRequest& request) {
+                        // mockReader ownership will be transferred to the client so we don't own it
+                        // here.
+                        MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+                                new MockClientReader<GetRemoteTasksResponse>();
+                        EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+                        EXPECT_CALL(*mockClientReader, Read(_))
+                                .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true)))
+                                .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true)))
+                                .WillRepeatedly(Return(false));
+                        return mockClientReader;
+                    });
+    // Should only be called once when is is ready for remote task.
+    EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(1);
+
+    getService()->setRemoteTaskCallback(callback);
+    setRetryWaitInMs(100);
+    // Start the long live connection to receive tasks.
+    ApState newState = {
+            .isReadyForRemoteTask = true,
+    };
+    ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+    ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+            << "Did not receive enough tasks";
+
+    // Stop the long live connection.
+    newState.isReadyForRemoteTask = false;
+    ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+    // Wait for the retry delay, but the loop should already exit.
+    std::this_thread::sleep_for(std::chrono::milliseconds(150));
+}
+
+}  // namespace remoteaccess
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
index 33a57d3..b6811b6 100644
--- a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
+++ b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
@@ -110,6 +110,16 @@
             "minSampleRate": 1.0
         },
         {
+            "property": "VehicleProperty::PERF_VEHICLE_SPEED_DISPLAY",
+            "defaultValue": {
+                "floatValues": [
+                    0.0
+                ]
+            },
+            "maxSampleRate": 10.0,
+            "minSampleRate": 1.0
+        },
+        {
             "property": "VehicleProperty::VEHICLE_SPEED_DISPLAY_UNITS",
             "defaultValue": {
                 "int32Values": [
@@ -136,6 +146,66 @@
             ]
         },
         {
+            "property": "VehicleProperty::SEAT_MEMORY_SELECT",
+            "defaultValue": {
+                "int32Values": [
+                    1
+                ]
+            },
+            "areas": [
+                {
+                    "areaId": "Constants::SEAT_1_LEFT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_1_RIGHT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_2_LEFT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_2_RIGHT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                }
+            ]
+        },
+        {
+            "property": "VehicleProperty::SEAT_MEMORY_SET",
+            "defaultValue": {
+                "int32Values": [
+                    1
+                ]
+            },
+            "areas": [
+                {
+                    "areaId": "Constants::SEAT_1_LEFT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_1_RIGHT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_2_LEFT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                },
+                {
+                    "areaId": "Constants::SEAT_2_RIGHT",
+                    "minInt32Value": 0,
+                    "maxInt32Value": 3
+                }
+            ]
+        },
+        {
             "property": "VehicleProperty::SEAT_BELT_BUCKLED",
             "defaultValue": {
                 "int32Values": [
diff --git a/automotive/vehicle/vts/Android.bp b/automotive/vehicle/vts/Android.bp
index 1cfd542..736787b 100644
--- a/automotive/vehicle/vts/Android.bp
+++ b/automotive/vehicle/vts/Android.bp
@@ -45,6 +45,7 @@
         "general-tests",
         "vts",
         "automotive-tests",
+        "automotive-general-tests",
     ],
     require_root: true,
 }
diff --git a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
index ebd728d..e9b74b7 100644
--- a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
+++ b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp
@@ -23,6 +23,7 @@
 #include <android/binder_process.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
+#include <cutils/properties.h>
 #include <fmq/AidlMessageQueue.h>
 
 #include <cstdint>
@@ -248,7 +249,8 @@
       case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH:
       case SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH:
       case SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH: {
-        ASSERT_FALSE(temp_provider_capabilities_.empty());
+        // empty capability means offload is unsupported since capabilities are
+        // not hardcoded
         for (auto audio_capability : temp_provider_capabilities_) {
           ASSERT_EQ(audio_capability.getTag(),
                     AudioCapabilities::leAudioCapabilities);
@@ -513,8 +515,9 @@
         for (auto channel_mode : opus_capability->channelMode) {
           OpusConfiguration opus_data{
               .samplingFrequencyHz = samplingFrequencyHz,
+              .frameDurationUs = frameDurationUs,
               .channelMode = channel_mode,
-              .frameDurationUs = frameDurationUs};
+          };
           opus_codec_specifics.push_back(
               CodecConfiguration::CodecSpecific(opus_data));
         }
@@ -623,8 +626,8 @@
       for (auto channel_mode : a2dp_channel_modes) {
         PcmConfiguration pcm_config{
             .sampleRateHz = sample_rate,
-            .bitsPerSample = bits_per_sample,
             .channelMode = channel_mode,
+            .bitsPerSample = bits_per_sample,
         };
         bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
         DataMQDesc mq_desc;
@@ -937,8 +940,8 @@
       for (auto channel_mode : hearing_aid_channel_modes_) {
         PcmConfiguration pcm_config{
             .sampleRateHz = sample_rate,
-            .bitsPerSample = bits_per_sample,
             .channelMode = channel_mode,
+            .bitsPerSample = bits_per_sample,
         };
         bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
         DataMQDesc mq_desc;
@@ -1008,8 +1011,8 @@
         for (auto data_interval_us : le_audio_output_data_interval_us_) {
           PcmConfiguration pcm_config{
               .sampleRateHz = sample_rate,
-              .bitsPerSample = bits_per_sample,
               .channelMode = channel_mode,
+              .bitsPerSample = bits_per_sample,
               .dataIntervalUs = data_interval_us,
           };
           bool is_codec_config_valid =
@@ -1081,8 +1084,8 @@
         for (auto data_interval_us : le_audio_input_data_interval_us_) {
           PcmConfiguration pcm_config{
               .sampleRateHz = sample_rate,
-              .bitsPerSample = bits_per_sample,
               .channelMode = channel_mode,
+              .bitsPerSample = bits_per_sample,
               .dataIntervalUs = data_interval_us,
           };
           bool is_codec_config_valid =
@@ -1144,7 +1147,7 @@
                                                            bool supported) {
     std::vector<Lc3Configuration> le_audio_codec_configs;
     if (!supported) {
-      Lc3Configuration lc3_config{.samplingFrequencyHz = 0, .pcmBitDepth = 0};
+      Lc3Configuration lc3_config{.pcmBitDepth = 0, .samplingFrequencyHz = 0};
       le_audio_codec_configs.push_back(lc3_config);
       return le_audio_codec_configs;
     }
@@ -1428,8 +1431,8 @@
         for (auto data_interval_us : le_audio_output_data_interval_us_) {
           PcmConfiguration pcm_config{
               .sampleRateHz = sample_rate,
-              .bitsPerSample = bits_per_sample,
               .channelMode = channel_mode,
+              .bitsPerSample = bits_per_sample,
               .dataIntervalUs = data_interval_us,
           };
           bool is_codec_config_valid =
@@ -1490,7 +1493,7 @@
   std::vector<Lc3Configuration> GetBroadcastLc3SupportedList(bool supported) {
     std::vector<Lc3Configuration> le_audio_codec_configs;
     if (!supported) {
-      Lc3Configuration lc3_config{.samplingFrequencyHz = 0, .pcmBitDepth = 0};
+      Lc3Configuration lc3_config{.pcmBitDepth = 0, .samplingFrequencyHz = 0};
       le_audio_codec_configs.push_back(lc3_config);
       return le_audio_codec_configs;
     }
@@ -1650,8 +1653,8 @@
       for (auto channel_mode : a2dp_channel_modes) {
         PcmConfiguration pcm_config{
             .sampleRateHz = sample_rate,
-            .bitsPerSample = bits_per_sample,
             .channelMode = channel_mode,
+            .bitsPerSample = bits_per_sample,
         };
         bool is_codec_config_valid = IsPcmConfigSupported(pcm_config);
         DataMQDesc mq_desc;
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
index 292d352..2b0caad 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.cpp
@@ -60,12 +60,14 @@
     LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_)
                << " MqDescriptor Invalid";
     audio_config_ = nullptr;
+    leaudio_connection_map_ = nullptr;
   } else {
     stack_iface_ = stack_iface;
     latency_modes_ = latency_modes;
     LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_)
               << ", AudioConfiguration=" << audio_config.toString();
     ReportSessionStatus();
+    is_streaming_ = false;
   }
 }
 
@@ -74,11 +76,13 @@
   bool toggled = IsSessionReady();
   LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_);
   audio_config_ = nullptr;
+  leaudio_connection_map_ = nullptr;
   stack_iface_ = nullptr;
   UpdateDataPath(nullptr);
   if (toggled) {
     ReportSessionStatus();
   }
+  is_streaming_ = false;
 }
 
 /***
@@ -106,18 +110,72 @@
   return *audio_config_;
 }
 
+const AudioConfiguration BluetoothAudioSession::GetLeAudioConnectionMap() {
+  std::lock_guard<std::recursive_mutex> guard(mutex_);
+  if (!IsSessionReady()) {
+    return AudioConfiguration(LeAudioConfiguration{});
+  }
+  return *leaudio_connection_map_;
+}
+
 void BluetoothAudioSession::ReportAudioConfigChanged(
     const AudioConfiguration& audio_config) {
   if (session_type_ !=
           SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH &&
       session_type_ !=
-          SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH &&
-      session_type_ !=
-          SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH) {
+          SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) {
     return;
   }
+
   std::lock_guard<std::recursive_mutex> guard(mutex_);
-  audio_config_ = std::make_unique<AudioConfiguration>(audio_config);
+  if (audio_config.getTag() != AudioConfiguration::leAudioConfig) {
+    LOG(ERROR) << __func__ << " invalid audio config type for SessionType ="
+               << toString(session_type_);
+    return;
+  }
+
+  if (is_streaming_) {
+    if (audio_config_ == nullptr) {
+      LOG(ERROR) << __func__ << " for SessionType=" << toString(session_type_)
+                 << " audio_config_ is nullptr during streaming. It shouldn't "
+                    "be happened";
+      return;
+    }
+
+    auto new_leaudio_config =
+        audio_config.get<AudioConfiguration::leAudioConfig>();
+    auto current_leaudio_config =
+        (*audio_config_).get<AudioConfiguration::leAudioConfig>();
+    if (new_leaudio_config.codecType != current_leaudio_config.codecType) {
+      LOG(ERROR)
+          << __func__ << " for SessionType=" << toString(session_type_)
+          << " codec type changed during streaming. It shouldn't be happened ";
+    }
+    auto new_lc3_config = new_leaudio_config.leAudioCodecConfig
+                              .get<LeAudioCodecConfiguration::lc3Config>();
+    auto current_lc3_config = current_leaudio_config.leAudioCodecConfig
+                                  .get<LeAudioCodecConfiguration::lc3Config>();
+    if ((new_lc3_config.pcmBitDepth != current_lc3_config.pcmBitDepth) ||
+        (new_lc3_config.samplingFrequencyHz !=
+         current_lc3_config.samplingFrequencyHz) ||
+        (new_lc3_config.frameDurationUs !=
+         current_lc3_config.frameDurationUs) ||
+        (new_lc3_config.octetsPerFrame != current_lc3_config.octetsPerFrame) ||
+        (new_lc3_config.blocksPerSdu != current_lc3_config.blocksPerSdu)) {
+      LOG(ERROR)
+          << __func__ << " for SessionType=" << toString(session_type_)
+          << " lc3 config changed during streaming. It shouldn't be happened";
+      return;
+    }
+
+    leaudio_connection_map_ =
+        std::make_unique<AudioConfiguration>(audio_config);
+  } else {
+    audio_config_ = std::make_unique<AudioConfiguration>(audio_config);
+    leaudio_connection_map_ =
+        std::make_unique<AudioConfiguration>(audio_config);
+  }
+
   if (observers_.empty()) {
     LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_)
                  << " has NO port state observer";
@@ -129,7 +187,11 @@
     LOG(INFO) << __func__ << " for SessionType=" << toString(session_type_)
               << ", bluetooth_audio=0x"
               << ::android::base::StringPrintf("%04x", cookie);
-    if (cb->audio_configuration_changed_cb_ != nullptr) {
+    if (is_streaming_) {
+      if (cb->soft_audio_configuration_changed_cb_ != nullptr) {
+        cb->soft_audio_configuration_changed_cb_(cookie);
+      }
+    } else if (cb->audio_configuration_changed_cb_ != nullptr) {
       cb->audio_configuration_changed_cb_(cookie);
     }
   }
@@ -419,6 +481,12 @@
                  << " has NO port state observer";
     return;
   }
+  if (start_resp && status == BluetoothAudioStatus::SUCCESS) {
+    is_streaming_ = true;
+  } else if (!start_resp && (status == BluetoothAudioStatus::SUCCESS ||
+                             status == BluetoothAudioStatus::RECONFIGURATION)) {
+    is_streaming_ = false;
+  }
   for (auto& observer : observers_) {
     uint16_t cookie = observer.first;
     std::shared_ptr<PortStatusCallbacks> callback = observer.second;
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
index 5bf17bd..faf4ffb 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSession.h
@@ -102,6 +102,13 @@
    ***/
   std::function<void(uint16_t cookie, bool allowed)>
       low_latency_mode_allowed_cb_;
+  /***
+   * soft_audio_configuration_changed_cb_ - when the Bluetooth stack change
+   * the streamMap during the streaming, the BluetoothAudioProvider will invoke
+   * this callback to report to the bluetooth_audio module.
+   * @param: cookie - indicates which bluetooth_audio output should handle
+   ***/
+  std::function<void(uint16_t cookie)> soft_audio_configuration_changed_cb_;
 };
 
 class BluetoothAudioSession {
@@ -159,6 +166,12 @@
   const AudioConfiguration GetAudioConfig();
 
   /***
+   * The control function is for the bluetooth_audio module to get the current
+   * LE audio connection map
+   ***/
+  const AudioConfiguration GetLeAudioConnectionMap();
+
+  /***
    * The report function is used to report that the Bluetooth stack has notified
    * the audio configuration changed, and will invoke
    * audio_configuration_changed_cb_ to notify registered bluetooth_audio
@@ -206,8 +219,11 @@
   std::unique_ptr<DataMQ> data_mq_;
   // audio data configuration for both software and offloading
   std::unique_ptr<AudioConfiguration> audio_config_;
+  std::unique_ptr<AudioConfiguration> leaudio_connection_map_;
   std::vector<LatencyMode> latency_modes_;
   bool low_latency_allowed_ = true;
+  // saving those steaming state based on the session_type
+  bool is_streaming_ = false;
 
   // saving those registered bluetooth_audio's callbacks
   std::unordered_map<uint16_t, std::shared_ptr<struct PortStatusCallbacks>>
diff --git a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
index 0782c82..881c6c1 100644
--- a/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
+++ b/bluetooth/audio/utils/aidl_session/BluetoothAudioSessionControl.h
@@ -95,6 +95,25 @@
   }
 
   /***
+   * The control API for the bluetooth_audio module to get current
+   * LE audio connection map
+   ***/
+  static const AudioConfiguration GetLeAudioConnectionMap(
+      const SessionType& session_type) {
+    std::shared_ptr<BluetoothAudioSession> session_ptr =
+        BluetoothAudioSessionInstance::GetSessionInstance(session_type);
+    if ((session_type ==
+             SessionType::LE_AUDIO_HARDWARE_OFFLOAD_ENCODING_DATAPATH ||
+         session_type ==
+             SessionType::LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH) &&
+        session_ptr != nullptr) {
+      return session_ptr->GetLeAudioConnectionMap();
+    }
+
+    return AudioConfiguration(LeAudioConfiguration{});
+  }
+
+  /***
    * Those control APIs for the bluetooth_audio module to start / suspend /
   stop
    * stream, to check position, and to update metadata.
diff --git a/broadcastradio/aidl/Android.bp b/broadcastradio/aidl/Android.bp
new file mode 100644
index 0000000..2d8078b
--- /dev/null
+++ b/broadcastradio/aidl/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+aidl_interface {
+    name: "android.hardware.broadcastradio",
+    vendor_available: true,
+    srcs: ["android/hardware/broadcastradio/*.aidl"],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: false,
+        },
+        java: {
+            sdk_version: "module_current",
+            min_sdk_version: "Tiramisu",
+        },
+    },
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmBandRange.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmBandRange.aidl
index 918ae3a..ca511aa 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmBandRange.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,11 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AmFmBandRange {
+  int lowerBound;
+  int upperBound;
+  int spacing;
+  int seekSpacing;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
similarity index 83%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
index 918ae3a..f3aecdf 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,14 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable AmFmRegionConfig {
+  android.hardware.broadcastradio.AmFmBandRange[] ranges;
+  int fmDeemphasis;
+  int fmRds;
+  const int DEEMPHASIS_D50 = 1;
+  const int DEEMPHASIS_D75 = 2;
+  const int RDS = 1;
+  const int RBDS = 2;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Announcement.aidl
similarity index 81%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Announcement.aidl
index 918ae3a..bbdd86f 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Announcement.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,10 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Announcement {
+  android.hardware.broadcastradio.ProgramSelector selector;
+  android.hardware.broadcastradio.AnnouncementType type = android.hardware.broadcastradio.AnnouncementType.INVALID;
+  android.hardware.broadcastradio.VendorKeyValue[] vendorInfo;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AnnouncementType.aidl
similarity index 85%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AnnouncementType.aidl
index 918ae3a..237b868 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AnnouncementType.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,16 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@Backing(type="byte") @JavaDerive(equals=true, toString=true) @VintfStability
+enum AnnouncementType {
+  INVALID = 0,
+  EMERGENCY = 1,
+  WARNING = 2,
+  TRAFFIC = 3,
+  WEATHER = 4,
+  NEWS = 5,
+  EVENT = 6,
+  SPORT = 7,
+  MISC = 8,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
similarity index 83%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
index 918ae3a..6fb9a62 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,16 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
+enum ConfigFlag {
+  FORCE_MONO = 1,
+  FORCE_ANALOG = 2,
+  FORCE_DIGITAL = 3,
+  RDS_AF = 4,
+  RDS_REG = 5,
+  DAB_DAB_LINKING = 6,
+  DAB_FM_LINKING = 7,
+  DAB_DAB_SOFT_LINKING = 8,
+  DAB_FM_SOFT_LINKING = 9,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/DabTableEntry.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/DabTableEntry.aidl
index 918ae3a..162f4abd 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/DabTableEntry.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable DabTableEntry {
+  String label;
+  int frequencyKhz;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IAnnouncementListener.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IAnnouncementListener.aidl
index 918ae3a..346af58 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IAnnouncementListener.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,8 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@VintfStability
+interface IAnnouncementListener {
+  oneway void onListUpdated(in android.hardware.broadcastradio.Announcement[] announcements);
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IBroadcastRadio.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IBroadcastRadio.aidl
new file mode 100644
index 0000000..39eb04c
--- /dev/null
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IBroadcastRadio.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.broadcastradio;
+@VintfStability
+interface IBroadcastRadio {
+  android.hardware.broadcastradio.Properties getProperties();
+  android.hardware.broadcastradio.AmFmRegionConfig getAmFmRegionConfig(in boolean full);
+  android.hardware.broadcastradio.DabTableEntry[] getDabRegionConfig();
+  void setTunerCallback(in android.hardware.broadcastradio.ITunerCallback callback);
+  void unsetTunerCallback();
+  void tune(in android.hardware.broadcastradio.ProgramSelector program);
+  void seek(in boolean directionUp, in boolean skipSubChannel);
+  void step(in boolean directionUp);
+  void cancel();
+  void startProgramListUpdates(in android.hardware.broadcastradio.ProgramFilter filter);
+  void stopProgramListUpdates();
+  boolean isConfigFlagSet(in android.hardware.broadcastradio.ConfigFlag flag);
+  void setConfigFlag(in android.hardware.broadcastradio.ConfigFlag flag, in boolean value);
+  android.hardware.broadcastradio.VendorKeyValue[] setParameters(in android.hardware.broadcastradio.VendorKeyValue[] parameters);
+  android.hardware.broadcastradio.VendorKeyValue[] getParameters(in String[] keys);
+  byte[] getImage(in int id);
+  android.hardware.broadcastradio.ICloseHandle registerAnnouncementListener(in android.hardware.broadcastradio.IAnnouncementListener listener, in android.hardware.broadcastradio.AnnouncementType[] enabled);
+  const int INVALID_IMAGE = 0;
+  const int ANTENNA_STATE_CHANGE_TIMEOUT_MS = 100;
+  const int LIST_COMPLETE_TIMEOUT_MS = 300000;
+  const int TUNER_TIMEOUT_MS = 30000;
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ICloseHandle.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ICloseHandle.aidl
index 918ae3a..75e7f2a 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ICloseHandle.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,8 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@VintfStability
+interface ICloseHandle {
+  void close();
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ITunerCallback.aidl
similarity index 71%
copy from radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ITunerCallback.aidl
index 930ff54..f5badad 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ITunerCallback.aidl
@@ -31,11 +31,13 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.network;
+package android.hardware.broadcastradio;
 @VintfStability
-parcelable NrRegistrationInfo {
-  android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
-  int extraInfo;
-  const int EXTRA_SMS_OVER_NAS_ALLOWED = 1;
-  const int EXTRA_REGISTERED_FOR_EMERGENCY = 2;
+interface ITunerCallback {
+  oneway void onTuneFailed(in android.hardware.broadcastradio.Result result, in android.hardware.broadcastradio.ProgramSelector selector);
+  oneway void onCurrentProgramInfoChanged(in android.hardware.broadcastradio.ProgramInfo info);
+  oneway void onProgramListUpdated(in android.hardware.broadcastradio.ProgramListChunk chunk);
+  oneway void onAntennaStateChange(in boolean connected);
+  oneway void onConfigFlagUpdated(in android.hardware.broadcastradio.ConfigFlag flag, in boolean value);
+  oneway void onParametersUpdated(in android.hardware.broadcastradio.VendorKeyValue[] parameters);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
similarity index 78%
copy from radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
index 930ff54..4e8296a 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
@@ -31,11 +31,22 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.network;
-@VintfStability
-parcelable NrRegistrationInfo {
-  android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
-  int extraInfo;
-  const int EXTRA_SMS_OVER_NAS_ALLOWED = 1;
-  const int EXTRA_REGISTERED_FOR_EMERGENCY = 2;
+package android.hardware.broadcastradio;
+@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
+enum IdentifierType {
+  VENDOR_START = 1000,
+  VENDOR_END = 1999,
+  INVALID = 0,
+  AMFM_FREQUENCY_KHZ = 1,
+  RDS_PI = 2,
+  HD_STATION_ID_EXT = 3,
+  HD_STATION_NAME = 4,
+  DAB_SID_EXT = 5,
+  DAB_ENSEMBLE = 6,
+  DAB_SCID = 7,
+  DAB_FREQUENCY_KHZ = 8,
+  DRMO_SERVICE_ID = 9,
+  DRMO_FREQUENCY_KHZ = 10,
+  SXM_SERVICE_ID = 12,
+  SXM_CHANNEL = 13,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
similarity index 77%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
index 918ae3a..e02b6b1 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,23 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+union Metadata {
+  String rdsPs;
+  int rdsPty;
+  int rbdsPty;
+  String rdsRt;
+  String songTitle;
+  String songArtist;
+  String songAlbum;
+  int stationIcon;
+  int albumArt;
+  String programName;
+  String dabEnsembleName;
+  String dabEnsembleNameShort;
+  String dabServiceName;
+  String dabServiceNameShort;
+  String dabComponentName;
+  String dabComponentNameShort;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramFilter.aidl
similarity index 83%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramFilter.aidl
index 918ae3a..9edeb8d 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramFilter.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,11 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ProgramFilter {
+  android.hardware.broadcastradio.IdentifierType[] identifierTypes;
+  android.hardware.broadcastradio.ProgramIdentifier[] identifiers;
+  boolean includeCategories;
+  boolean excludeModifications;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramIdentifier.aidl
similarity index 86%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramIdentifier.aidl
index 918ae3a..6676350 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramIdentifier.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ProgramIdentifier {
+  android.hardware.broadcastradio.IdentifierType type = android.hardware.broadcastradio.IdentifierType.INVALID;
+  long value;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
similarity index 68%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
index 918ae3a..5e662d2 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,21 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ProgramInfo {
+  android.hardware.broadcastradio.ProgramSelector selector;
+  android.hardware.broadcastradio.ProgramIdentifier logicallyTunedTo;
+  android.hardware.broadcastradio.ProgramIdentifier physicallyTunedTo;
+  @nullable android.hardware.broadcastradio.ProgramIdentifier[] relatedContent;
+  int infoFlags;
+  int signalQuality;
+  android.hardware.broadcastradio.Metadata[] metadata;
+  android.hardware.broadcastradio.VendorKeyValue[] vendorInfo;
+  const int FLAG_LIVE = 1;
+  const int FLAG_MUTED = 2;
+  const int FLAG_TRAFFIC_PROGRAM = 4;
+  const int FLAG_TRAFFIC_ANNOUNCEMENT = 8;
+  const int FLAG_TUNABLE = 16;
+  const int FLAG_STEREO = 32;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramListChunk.aidl
similarity index 84%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramListChunk.aidl
index 918ae3a..5d53b99 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramListChunk.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,11 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ProgramListChunk {
+  boolean purge;
+  boolean complete;
+  android.hardware.broadcastradio.ProgramInfo[] modified;
+  @nullable android.hardware.broadcastradio.ProgramIdentifier[] removed;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramSelector.aidl
similarity index 86%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramSelector.aidl
index 918ae3a..9af1dc8 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramSelector.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable ProgramSelector {
+  android.hardware.broadcastradio.ProgramIdentifier primaryId;
+  android.hardware.broadcastradio.ProgramIdentifier[] secondaryIds;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Properties.aidl
similarity index 83%
rename from radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
rename to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Properties.aidl
index 930ff54..643b819 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Properties.aidl
@@ -31,11 +31,13 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.network;
-@VintfStability
-parcelable NrRegistrationInfo {
-  android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
-  int extraInfo;
-  const int EXTRA_SMS_OVER_NAS_ALLOWED = 1;
-  const int EXTRA_REGISTERED_FOR_EMERGENCY = 2;
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable Properties {
+  String maker;
+  String product;
+  String version;
+  String serial;
+  android.hardware.broadcastradio.IdentifierType[] supportedIdentifierTypes;
+  android.hardware.broadcastradio.VendorKeyValue[] vendorInfo;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
similarity index 87%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
index 918ae3a..07edae8 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,14 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@Backing(type="int") @VintfStability
+enum Result {
+  OK = 0,
+  INTERNAL_ERROR = 1,
+  INVALID_ARGUMENTS = 2,
+  INVALID_STATE = 3,
+  NOT_SUPPORTED = 4,
+  TIMEOUT = 5,
+  UNKNOWN_ERROR = 6,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/VendorKeyValue.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/VendorKeyValue.aidl
index 918ae3a..3c6b194 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/VendorKeyValue.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.broadcastradio;
+@JavaDerive(equals=true, toString=true) @VintfStability
+parcelable VendorKeyValue {
+  String key;
+  String value;
 }
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/AmFmBandRange.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/AmFmBandRange.aidl
new file mode 100644
index 0000000..562631f
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/AmFmBandRange.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Defines the AM/FM band range for configuring different regions.
+ *
+ * <p>Channel grid is defined as: each possible channel is set at
+ * lowerBound + channelNumber * spacing, up to upperBound.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable AmFmBandRange {
+    /**
+     * The frequency (in kHz) of the first channel within the range.
+     *
+     * Lower bound must be a tunable frequency.
+     */
+    int lowerBound;
+
+    /**
+     * The frequency (in kHz) of the last channel within the range.
+     */
+    int upperBound;
+
+    /**
+     * Channel grid resolution (in kHz), telling how far the channels are apart.
+     */
+    int spacing;
+
+    /**
+     * Channel spacing (in kHz) used to speed up seeking to the next station
+     * via the {@link IBroadcastRadio#seek} operation.
+     *
+     * It must be a multiple of channel grid resolution.
+     *
+     * Tuner may first quickly check every n-th channel and if it detects echo
+     * from a station, it fine-tunes to find the exact frequency.
+     *
+     * It's ignored for capabilities check (with full=true when calling
+     * getAmFmRegionConfig).
+     */
+    int seekSpacing;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/AmFmRegionConfig.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/AmFmRegionConfig.aidl
new file mode 100644
index 0000000..a3086c6
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/AmFmRegionConfig.aidl
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.AmFmBandRange;
+
+/**
+ * Regional configuration for AM/FM.
+ *
+ * <p>For hardware capabilities check (with full=true when calling
+ * {@link IBroadcastRadio#getAmFmRegionConfig}), HAL implementation fills
+ * entire supported range of frequencies and features.
+ *
+ * When checking current configuration, at most one bit in each bitset
+ * can be set.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable AmFmRegionConfig {
+    /**
+     * Noth D50 and D75 are FM de-emphasis filter supported or configured.
+     *
+     * Both might be set for hardware capabilities check (with full={@code true}
+     * when calling getAmFmRegionConfig), but exactly one for specific region
+     * settings.
+     */
+    const int DEEMPHASIS_D50 = 1 << 0;
+
+    const int DEEMPHASIS_D75 = 1 << 1;
+
+    /**
+     * Both RDS and RBDS are supported or configured RDS variants.
+     *
+     * Both might be set for hardware capabilities check (with full={@code true}
+     * when calling getAmFmRegionConfig), but only one (or none) for specific
+     * region settings.
+     *
+     * RDS is Standard variant, used everywhere except North America.
+     */
+    const int RDS = 1 << 0;
+
+    /**
+     * Variant used in North America (see RDS).
+     */
+    const int RBDS = 1 << 1;
+
+    /**
+     * All supported or configured AM/FM bands.
+     *
+     * AM/FM bands are identified by frequency value
+     * (see {@link IdentifierType#AMFM_FREQUENCY_KHZ}).
+     *
+     * With typical configuration, it's expected to have two frequency ranges
+     * for capabilities check (AM and FM) and four ranges for specific region
+     * configuration (AM LW, AM MW, AM SW, FM).
+     */
+    AmFmBandRange[] ranges;
+
+    /**
+     * De-emphasis filter supported/configured.
+     *
+     * It is a bitset of de-emphasis values (DEEMPHASIS_D50 and DEEMPHASIS_D75).
+     */
+    int fmDeemphasis;
+
+    /**
+     * RDS/RBDS variant supported/configured.
+     *
+     * It is a bitset of RDS values (RDS and RBDS).
+     */
+    int fmRds;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Announcement.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Announcement.aidl
new file mode 100644
index 0000000..a972d4d
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Announcement.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.AnnouncementType;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.VendorKeyValue;
+
+/**
+ * Station broadcasting active announcement.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable Announcement {
+    /**
+     * Program selector to tune to the announcement.
+     */
+    ProgramSelector selector;
+
+    /**
+     * Announcement type.
+     */
+    AnnouncementType type = AnnouncementType.INVALID;
+
+    /**
+     * Vendor-specific information.
+     *
+     * It may be used for extra features, not supported by the platform,
+     * for example: com.me.hdradio.urgency=100; com.me.hdradio.certainity=50.
+     */
+    VendorKeyValue[] vendorInfo;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/AnnouncementType.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/AnnouncementType.aidl
new file mode 100644
index 0000000..8df8025
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/AnnouncementType.aidl
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Type of an announcement.
+ *
+ * <p>It maps to different announcement types for each radio technology.
+ */
+@VintfStability
+@Backing(type="byte")
+@JavaDerive(equals=true, toString=true)
+enum AnnouncementType {
+    /**
+     * Undefined announcement type
+     */
+    INVALID = 0,
+
+    /**
+     * DAB alarm, RDS emergency program type (PTY 31).
+     */
+    EMERGENCY = 1,
+
+    /**
+     * DAB warning.
+     */
+    WARNING,
+
+    /**
+     * DAB road traffic, RDS TA, HD Radio transportation.
+     */
+    TRAFFIC,
+
+    /**
+     * Weather.
+     */
+    WEATHER,
+
+    /**
+     * News.
+     */
+    NEWS,
+
+    /**
+     * DAB event, special event.
+     */
+    EVENT,
+
+    /**
+     * DAB sport report, RDS sports.
+     */
+    SPORT,
+
+    /**
+     * All others.
+     */
+    MISC,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
new file mode 100644
index 0000000..11da39c
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Configuration flags to be used with isConfigFlagSet and setConfigFlag methods
+ * of IBroadcastRadio.
+ */
+@VintfStability
+@Backing(type="int")
+@JavaDerive(equals=true, toString=true)
+enum ConfigFlag {
+    /**
+     * Forces mono audio stream reception.
+     *
+     * Analog broadcasts can recover poor reception conditions by jointing
+     * stereo channels into one. Mainly for, but not limited to AM/FM.
+     */
+    FORCE_MONO = 1,
+
+    /**
+     * Forces the analog playback for the supporting radio technology.
+     *
+     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * this option. This is purely user choice, ie. does not reflect digital-
+     * analog handover state managed from the HAL implementation side.
+     *
+     * Some radio technologies may not support this, ie. DAB.
+     */
+    FORCE_ANALOG,
+
+    /**
+     * Forces the digital playback for the supporting radio technology.
+     *
+     * User may disable digital-analog handover that happens with poor
+     * reception conditions. With digital forced, the radio will remain silent
+     * instead of switching to analog channel if it's available. This is purely
+     * user choice, it does not reflect the actual state of handover.
+     */
+    FORCE_DIGITAL,
+
+    /**
+     * RDS Alternative Frequencies.
+     *
+     * If set and the currently tuned RDS station broadcasts on multiple
+     * channels, radio tuner automatically switches to the best available
+     * alternative.
+     */
+    RDS_AF,
+
+    /**
+     * RDS region-specific program lock-down.
+     *
+     * Allows user to lock to the current region as they move into the
+     * other region.
+     */
+    RDS_REG,
+
+    /**
+     * Enables DAB-DAB hard- and implicit-linking (the same content).
+     */
+    DAB_DAB_LINKING,
+
+    /**
+     * Enables DAB-FM hard- and implicit-linking (the same content).
+     */
+    DAB_FM_LINKING,
+
+    /**
+     * Enables DAB-DAB soft-linking (related content).
+     */
+    DAB_DAB_SOFT_LINKING,
+
+    /**
+     * Enables DAB-FM soft-linking (related content).
+     */
+    DAB_FM_SOFT_LINKING,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/DabTableEntry.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/DabTableEntry.aidl
new file mode 100644
index 0000000..13c20b0
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/DabTableEntry.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * An entry in regional configuration for DAB.
+ *
+ * <p>This defines a frequency table row for ensembles.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable DabTableEntry {
+    /**
+     * Channel name, i.e. 5A, 7B.
+     *
+     * It must match the following regular expression:
+     * /^[A-Z0-9][A-Z0-9 ]{0,5}[A-Z0-9]$/ (2-7 uppercase alphanumeric characters
+     * without spaces allowed at the beginning nor end).
+     */
+    String label;
+
+    /**
+     * Frequency, in kHz.
+     */
+    int frequencyKhz;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/IAnnouncementListener.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/IAnnouncementListener.aidl
new file mode 100644
index 0000000..f6021c1
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/IAnnouncementListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.Announcement;
+
+/**
+ * Callback interface for announcement listener.
+ *
+ * For typical configuration, the listener is a broadcast radio service.
+ */
+@VintfStability
+interface IAnnouncementListener {
+    /**
+     * Called whenever announcement list has changed.
+     *
+     * @param announcements The complete list of currently active announcements.
+     */
+    oneway void onListUpdated(in Announcement[] announcements);
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/IBroadcastRadio.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/IBroadcastRadio.aidl
new file mode 100644
index 0000000..0f88fc0
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/IBroadcastRadio.aidl
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.AnnouncementType;
+import android.hardware.broadcastradio.ConfigFlag;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IAnnouncementListener;
+import android.hardware.broadcastradio.ICloseHandle;
+import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.ProgramFilter;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.VendorKeyValue;
+
+/**
+ * Represents a hardware broadcast radio module. A single module may contain
+ * multiple hardware tuners (i.e. with an additional background tuner), but the
+ * layers above the HAL see them as a single logical unit.
+ */
+@VintfStability
+interface IBroadcastRadio {
+    /**
+     * Invalid identifier for {@link IBroadcastRadio#getImage}.
+     */
+    const int INVALID_IMAGE = 0;
+
+    /**
+     * If the antenna is disconnected from the beginning, the
+     * {@link ITunerCallback#onAntennaStateChange} callback must be
+     * called within this time.
+     */
+    const int ANTENNA_STATE_CHANGE_TIMEOUT_MS = 100;
+
+    /**
+     * All chunks of a signal program list update must be transmitted
+     * within this time.
+     */
+    const int LIST_COMPLETE_TIMEOUT_MS = 300000;
+
+    /**
+     * All tune, seek and step operations must be completed within
+     * this time.
+     */
+    const int TUNER_TIMEOUT_MS = 30000;
+
+    /**
+     * Returns module properties: a description of a module and its
+     * capabilities. This method must not fail.
+     *
+     * @return Module description.
+     */
+    Properties getProperties();
+
+    /**
+     * Fetches current or possible AM/FM region configuration.
+     *
+     * If the tuner doesn't support AM/FM, a service-specific error
+     * {@link Result#NOT_SUPPORTED} will be returned.
+     *
+     * @param full If {@code true}, returns full hardware capabilities.
+     *             If {@code false}, returns current regional configuration.
+     * @return config Hardware capabilities (full={@code true}) or current configuration
+     *                (full={@code false}).
+     */
+    AmFmRegionConfig getAmFmRegionConfig(in boolean full);
+
+    /**
+     * Fetches current DAB region configuration.
+     *
+     * If tuner doesn't support DAB, a service-specific error
+     * {@link Result#NOT_SUPPORTED} wiil be returned.
+     *
+     * @return config Current configuration.
+     */
+    DabTableEntry[] getDabRegionConfig();
+
+    /**
+     * Sets callback interface.
+     *
+     * It is expected that there will only ever be a single callback set.
+     * If called when a callback is already set, the existing one should be
+     * replaced with the new callback.
+     *
+     * If the callback to be set is null, a service-specific error
+     * {@link Result#INVALID_ARGUMENTS} will be returned; if the callback
+     * is not set successfully, a service-specific error
+     * {@link Result#NOT_SUPPORTED} should be returned.
+     *
+     * @param callback The callback interface used for BroadcastRadio HAL.
+     */
+    void setTunerCallback(in ITunerCallback callback);
+
+    /**
+     * Unsets callback interface.
+     *
+     * The existing callback is set to null.
+     */
+    void unsetTunerCallback();
+
+    /**
+     * Tunes to a specified program.
+     *
+     * Automatically cancels pending tune(), seek() or step().
+     * The method should first check whether tune can be processed by the status
+     * of tuner and inputs, schedule tune task, and then return status
+     * immediately. If a non-null callback is not set, a service-specific
+     * error {@link Result#INVALID_STATE} will be returned; if the program
+     * selector doesn't contain any supported identifier, a service-specific error
+     * {@link Result#NOT_SUPPORTED} will be returned; if the program selector
+     * contains identifiers in invalid format (i.e. out of range), a
+     * service-specific error {@link Result#INVALID_ARGUMENTS} will be returned;
+     * otherwise, OK will be returned as status. Tune task should be processed
+     * asynchronously after the method returns status. If the method returns OK,
+     * {@link ITunerCallback#tuneFailed} or
+     * {@link ITunerCallback#currentProgramInfoChanged} callback must be called
+     * after the tune task completes.
+     *
+     * @param program Program to tune to.
+     */
+    void tune(in ProgramSelector program);
+
+    /**
+     * Seeks the next valid program on the "air".
+     *
+     * Advance to the next detected program and stay there.
+     *
+     * Automatically cancels pending tune(), seek() or step().
+     * The method should first check whether seek can be processed by the status
+     * of tuner and inputs, schedule seek task, and then return status
+     * immediately. If a non-null callback is not set, a service-specific
+     * error {@link Result#INVALID_STATE} will be returned; otherwise, OK will
+     * be returned as status. Seek task should be processed asynchronously
+     * after the method returns status. If the method returns OK,
+     * {@link ITunerCallback#tuneFailed} or
+     * {@link ITunerCallback#currentProgramInfoChanged} callback must be called
+     * after the seek task completes.
+     *
+     * The skipSubChannel parameter is used to skip digital radio subchannels:
+     *  - HD Radio SPS;
+     *  - DAB secondary service.
+     *
+     * As an implementation detail, the HAL has the option to perform an actual
+     * seek or select the next program from the list retrieved in the
+     * background.
+     *
+     * @param directionUp {@code true} to change towards higher numeric values
+     *                    (frequency, channel number), {@code false} towards
+     *                    lower.
+     * @param skipSubChannel Don't tune to subchannels.
+     */
+    void seek(in boolean directionUp, in boolean skipSubChannel);
+
+    /**
+     * Steps to the adjacent channel, which may not be occupied by any program.
+     *
+     * Automatically cancels pending tune(), seek() or step().
+     * The method should first check whether step can be processed by the status
+     * of tuner and inputs, schedule step task, and then return status
+     * immediately. If a non-null callback is not set, service-specific
+     * error {@link Result#INVALID_STATE} will be returned; if tuning to an
+     * unoccupied channel is not supported (i.e. for satellite radio), a
+     * service-specific error {@link Result#NOT_SUPPORTED} will be returned;
+     * otherwise, OK should be returned as status. Step task should be
+     * processed asynchronously after the method returns status. If the
+     * method returns OK, {@link ITunerCallback#tuneFailed} or
+     * {@link currentProgramInfoChanged} callback must be called after the
+     * step task completes.
+     *
+     * @param directionUp {@code true} to change towards higher numeric values
+     *                    (frequency, channel number), {@code false} towards lower.
+     */
+    void step(in boolean directionUp);
+
+    /**
+     * Cancels pending tune(), seek() or step().
+     *
+     * If there is no such operation running, the call can be ignored.
+     * If cancel is called after the HAL completes an operation (tune, seek, and step)
+     * and before the callback completions, the cancel can be ignored and the callback
+     * should complete.
+     */
+    void cancel();
+
+    /**
+     * Applies a filter to the program list and starts sending program list
+     * update over {@link ITunerCallback#onProgramListUpdated} callback.
+     *
+     * There may be only one updates stream active at the moment. Calling this
+     * method again must result in cancelling the pending update request.
+     *
+     * This call clears the program list on the client side, the HAL must send
+     * the whole list again.
+     *
+     * If the program list scanning hardware (i.e. background tuner) is
+     * unavailable at the moment, the call must succeed and start updates
+     * when it becomes available.
+     *
+     * If the program list scanning is not supported by the hardware, a
+     * service-specific error {@link Result#NOT_SUPPORTED} will be returned.
+     *
+     * @param filter Filter to apply on the fetched program list.
+     */
+    void startProgramListUpdates(in ProgramFilter filter);
+
+    /**
+     * Stops sending program list updates.
+     *
+     * If stopProgramListUpdates is called after the HAL completes a program list update
+     * and before the onCurrentProgramInfoChanged callback completions,
+     * stopProgramListUpdates can be ignored and the callback should complete.
+     */
+    void stopProgramListUpdates();
+
+    /**
+     * Fetches the current setting of a given config flag.
+     *
+     * The success/failure result must be consistent with setConfigFlag.
+     *
+     * If the flag is not applicable, a service-specific error
+     * {@link Result#INVALID_STATE} will be returned. If the flag is not
+     * supported at all, a service-specific error {@link Result#NOT_SUPPORTED}
+     * will be returned.
+     *
+     * @return the current value of the flag, if succeed.
+     */
+    boolean isConfigFlagSet(in ConfigFlag flag);
+
+    /**
+     * Sets the config flag.
+     *
+     * The success/failure result must be consistent with isConfigFlagSet.
+     *
+     * If the flag is not applicable, a service-specific error
+     * {@link Result#INVALID_STATE} will be returned. If the flag is not
+     * supported at all, a service-specific error {@link Result#NOT_SUPPORTED}
+     * will be returned.
+     *
+     * @param flag Flag to set.
+     * @param value The new value of a given flag.
+     */
+    void setConfigFlag(in ConfigFlag flag, in boolean value);
+
+    /**
+     * Generic method for setting vendor-specific parameter values.
+     * The framework does not interpret the parameters, they are passed
+     * in an opaque manner between a vendor application and HAL.
+     *
+     * Framework does not make any assumptions on the keys or values, other than
+     * ones stated in VendorKeyValue documentation (a requirement of key
+     * prefixes).
+     *
+     * For each pair in the result array, the key must be one of the keys
+     * contained in the input (possibly with wildcards expanded), and the value
+     * must be a vendor-specific result status (i.e. the string "OK" or an error
+     * code). The implementation may choose to return an empty array, or only
+     * return a status for a subset of the provided inputs, at its discretion.
+     *
+     * Application and HAL must not use keys with unknown prefix. In particular,
+     * it must not place a key-value pair in results array for unknown key from
+     * parameters array - instead, an unknown key should simply be ignored.
+     * In other words, results array may contain a subset of parameter keys
+     * (however, the framework doesn't enforce a strict subset - the only
+     * formal requirement is vendor domain prefix for keys).
+     *
+     * @param parameters Vendor-specific key-value pairs.
+     * @return Operation completion status for parameters being set.
+     */
+    VendorKeyValue[] setParameters(in VendorKeyValue[] parameters);
+
+    /**
+     * Generic method for retrieving vendor-specific parameter values.
+     * The framework does not interpret the parameters, they are passed
+     * in an opaque manner between a vendor application and HAL.
+     *
+     * Framework does not cache set/get requests, so it's allowed for
+     * getParameter to return a different value than previous setParameter call.
+     *
+     * The syntax and semantics of keys are up to the vendor (as long as prefix
+     * rules are obeyed). For instance, vendors may include some form of
+     * wildcard support. In such case, result array may be of different size
+     * than requested keys array. However, wildcards are not recognized by
+     * framework and they are passed as-is to the HAL implementation.
+     *
+     * Unknown keys must be ignored and not placed into results array.
+     *
+     * @param keys Parameter keys to fetch.
+     * @return Vendor-specific key-value pairs.
+     */
+    VendorKeyValue[] getParameters(in String[] keys);
+
+    /**
+     * Fetches image from radio module cache.
+     *
+     * This is out-of-band transport mechanism for images carried with metadata.
+     * The metadata array only passes the identifier, so the client may cache
+     * images or even not fetch them.
+     *
+     * The identifier may be any arbitrary number (i.e. sha256 prefix) selected
+     * by the vendor. It must be stable so the application may cache it.
+     *
+     * The data must be a valid PNG, JPEG, GIF or BMP file, and must be less
+     * than 1MB, due to hard limit on binder transaction buffer.
+     *
+     * Image data with an invalid format must be handled gracefully in the same
+     * way as a missing image.
+     *
+     * The image identifier may become invalid after some time from passing it
+     * with metadata struct (due to resource cleanup at the HAL implementation).
+     * However, it must remain valid for a currently tuned program at least
+     * until onCurrentProgramInfoChanged is called.
+     *
+     * @param id Identifier of an image (value of {@link IBroadcastRadio#INVALID_IMAGE}
+     *           is reserved and must be treated as invalid image).
+     * @return A binary blob with image data
+     *         or a zero-length array if identifier doesn't exist.
+     */
+    byte[] getImage(in int id);
+
+    /**
+     * Registers announcement listener.
+     *
+     * If there is at least one observer registered, HAL implementation must
+     * notify about announcements.
+     *
+     * If the observer dies, the HAL implementation must unregister observer
+     * automatically.
+     *
+     * If the tuner doesn't support announcements, a service-specific error
+     * {@link Result#NOT_SUPPORTED} will be returned.
+     *
+     * @param listener The listener interface.
+     * @param enabled The list of announcement types to watch for.
+     * @return a handle to unregister observer.
+     */
+    ICloseHandle registerAnnouncementListener(
+            in IAnnouncementListener listener, in AnnouncementType[] enabled);
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ICloseHandle.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ICloseHandle.aidl
new file mode 100644
index 0000000..4a3240b
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ICloseHandle.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Represents a generic close handle to remove a callback that doesn't need
+ * active interface.
+ */
+@VintfStability
+interface ICloseHandle {
+    /**
+     * Closes the handle.
+     *
+     * The call must not fail and must only be issued once.
+     *
+     * After the close call is executed, no other calls to this interface
+     * are allowed. If the call is issued second time, a service-specific
+     * error {@link Result#INVALID_STATE} will be returned.
+     */
+    void close();
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ITunerCallback.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ITunerCallback.aidl
new file mode 100644
index 0000000..1f0221c
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ITunerCallback.aidl
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.ConfigFlag;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.Result;
+import android.hardware.broadcastradio.VendorKeyValue;
+
+@VintfStability
+oneway interface ITunerCallback {
+    /**
+     * Method called by the HAL when a tuning operation fails asynchronously
+     * following {@link IBroadcastRadio#tune}, {@link IBroadcastRadio#seek}
+     * or {@link IBroadcastRadio#step}.
+     *
+     * This callback is only called when the tune(), seek() or step() command
+     * succeeds without returning any error at first.
+     *
+     * @param result {@link Result#TIMEOUT} in case that tune(), seek() or
+     *               step() is not completed within
+     *               @link IBroadcastRadio#TUNER_TIMEOUT_MS}
+     * @param selector A ProgramSelector structure passed from tune() call;
+     *                 empty for step() and seek().
+     */
+    void onTuneFailed(in Result result, in ProgramSelector selector);
+
+    /**
+     * Method called by the HAL when current program information (including
+     * metadata) is updated. It must be called when {@link IBroadcastRadio#tune}
+     * {@link IBroadcastRadio#seek} or {@link IBroadcastRadio#step} command
+     * succeeds.
+     *
+     * This is also called when the radio tuned to the static (not a valid
+     * station), see {@link ProgramInfo#FLAG_TUNABLE} flag.
+     *
+     * @param info Current program information.
+     */
+    void onCurrentProgramInfoChanged(in ProgramInfo info);
+
+    /**
+     * A delta update of the program list, called whenever there's a change in
+     * the list.
+     *
+     * If there are frequent changes, HAL implementation must throttle the rate
+     * of the updates.
+     *
+     * There is a hard limit on binder transaction buffer, and the list must
+     * not exceed it. For large lists, HAL implementation must split them to
+     * multiple chunks, no larger than 500kiB each, and call this program list
+     * update callback method separately.
+     *
+     * @param chunk A chunk of the program list update.
+     */
+    void onProgramListUpdated(in ProgramListChunk chunk);
+
+    /**
+     * Method called by the HAL when the antenna gets connected or disconnected.
+     *
+     * For broadcast radio service, client must assume the antenna is connected.
+     * If it's not, then antennaStateChange must be called within
+     * {@link IBroadcastRadio#ANTENNA_STATE_CHANGE_TIMEOUT_MS} to indicate that.
+     *
+     * @param connected {@code true} if the antenna is now connected, {@code false}
+     * otherwise.
+     */
+    void onAntennaStateChange(in boolean connected);
+
+    /**
+     * Generic callback for passing updates to config flags.
+     *
+     * It's up to the HAL implementation if and how to implement this callback,
+     * as long as it obeys the prefix rule. However, setConfigFlag must not
+     * trigger this callback, while an internal event can change config flag
+     * asynchronously at the HAL layer.
+     *
+     * @param flag Flag that has changed.
+     * @param value The new value of the given flag.
+     */
+    void onConfigFlagUpdated(in ConfigFlag flag, in boolean value);
+
+    /**
+     * Generic callback for passing updates to vendor-specific parameter values.
+     * The framework does not interpret the parameters, they are passed
+     * in an opaque manner between a vendor application and HAL.
+     *
+     * It's up to the HAL implementation if and how to implement this callback,
+     * as long as it obeys the prefix rule. In particular, only selected keys
+     * may be notified this way. However, setParameters must not trigger
+     * this callback, while an internal event can change parameters
+     * asynchronously at the HAL layer.
+     *
+     * @param parameters Vendor-specific key-value pairs,
+     *                   opaque to Android framework.
+     */
+    void onParametersUpdated(in VendorKeyValue[] parameters);
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
new file mode 100644
index 0000000..0484d02
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Type of program identifier component.
+ *
+ * Each identifier type corresponds to exactly one radio technology,
+ * i.e. DAB_ENSEMBLE is specifically for DAB.
+ *
+ * VENDOR identifier types must be opaque to the framework.
+ *
+ * The value format for each (but VENDOR_*) identifier is strictly defined
+ * to maintain interoperability between devices made by different vendors.
+ *
+ * All other values are reserved for future use.
+ * Values not matching any enumerated constant must be ignored.
+ */
+@VintfStability
+@Backing(type="int")
+@JavaDerive(equals=true, toString=true)
+enum IdentifierType {
+    /**
+     * Primary/secondary identifier for vendor-specific radio technology.
+     * The value format is determined by a vendor.
+     *
+     * The vendor identifiers have limited serialization capabilities - see
+     * ProgramSelector description.
+     */
+    VENDOR_START = 1000,
+
+    /**
+     * See VENDOR_START
+     */
+    VENDOR_END = 1999,
+
+    /**
+     * Undefined identifier type.
+     */
+    INVALID = 0,
+
+    /**
+     * Primary identifier for analogue (without RDS) AM/FM stations:
+     * frequency in kHz.
+     *
+     * This identifier also contains band information:
+     *  - <500kHz: AM LW;
+     *  - 500kHz - 1705kHz: AM MW;
+     *  - 1.71MHz - 30MHz: AM SW;
+     *  - >60MHz: FM.
+     */
+    AMFM_FREQUENCY_KHZ,
+
+    /**
+     * 16bit primary identifier for FM RDS station.
+     */
+    RDS_PI,
+
+    /**
+     * 64bit compound primary identifier for HD Radio.
+     *
+     * Consists of (from the LSB):
+     * - 32bit: Station ID number;
+     * - 4bit: HD Radio subchannel;
+     * - 18bit: AMFM_FREQUENCY_KHZ.
+     *
+     * While station ID number should be unique globally, it sometimes get
+     * abused by broadcasters (i.e. not being set at all). To ensure local
+     * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
+     * a best-effort - see HD_STATION_NAME.
+     *
+     * HD Radio subchannel is a value in range 0-7.
+     * This index is 0-based (where 0 is MPS and 1..7 are SPS),
+     * as opposed to HD Radio standard (where it's 1-based).
+     *
+     * The remaining bits should be set to zeros when writing on the chip side
+     * and ignored when read.
+     */
+
+    HD_STATION_ID_EXT,
+
+    /**
+     * 64bit additional identifier for HD Radio.
+     *
+     * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+     * globally unique. To provide a best-effort solution, a short version of
+     * station name may be carried as additional identifier and may be used
+     * by the tuner hardware to double-check tuning.
+     *
+     * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
+     * must be converted to uppercase). Encoded in little-endian ASCII:
+     * the first character of the name is the LSB.
+     *
+     * For example: "Abc" is encoded as 0x434241.
+     */
+    HD_STATION_NAME,
+
+    /**
+     * 28bit compound primary identifier for Digital Audio Broadcasting.
+     *
+     * Consists of (from the LSB):
+     * - 16bit: SId;
+     * - 8bit: ECC code;
+     * - 4bit: SCIdS.
+     *
+     * SCIdS (Service Component Identifier within the Service) value
+     * of 0 represents the main service, while 1 and above represents
+     * secondary services.
+     *
+     * The remaining bits should be set to zeros when writing on the chip side
+     * and ignored when read.
+     */
+    DAB_SID_EXT,
+
+    /**
+     * 16bit
+     */
+    DAB_ENSEMBLE,
+
+    /**
+     * 12bit
+     */
+    DAB_SCID,
+
+    /**
+     * kHz (see AMFM_FREQUENCY_KHZ)
+     */
+    DAB_FREQUENCY_KHZ,
+
+    /**
+     * 24bit primary identifier for Digital Radio Mondiale.
+     */
+    DRMO_SERVICE_ID,
+
+    /**
+     * kHz (see AMFM_FREQUENCY_KHZ)
+     */
+    DRMO_FREQUENCY_KHZ,
+
+    /**
+     * 32bit primary identifier for SiriusXM Satellite Radio.
+     */
+    SXM_SERVICE_ID = DRMO_FREQUENCY_KHZ + 2,
+
+    /**
+     * 0-999 range
+     */
+    SXM_CHANNEL,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
new file mode 100644
index 0000000..3298cac
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * An element of metadata array.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+union Metadata {
+    /**
+     * RDS PS (string)
+     */
+    String rdsPs;
+
+    /**
+     * RDS PTY (uint8_t)
+     */
+    int rdsPty;
+
+    /**
+     * RBDS PTY (uint8_t)
+     */
+    int rbdsPty;
+
+    /**
+     * RDS RT (string)
+     */
+    String rdsRt;
+
+    /**
+     * Song title (string)
+     */
+    String songTitle;
+
+    /**
+     * Artist name (string)
+     */
+    String songArtist;
+
+    /**
+     * Album name (string)
+     */
+    String songAlbum;
+
+    /**
+     * Station icon (uint32_t, see {@link IBroadcastRadio#getImage})
+     */
+    int stationIcon;
+
+    /**
+     * Album art (uint32_t, see {@link IBroadcastRadio#getImage})
+     */
+    int albumArt;
+
+    /**
+     * Station name.
+     *
+     * This is a generic field to cover any radio technology.
+     *
+     * If the PROGRAM_NAME has the same content as DAB_*_NAME or RDS_PS,
+     * it may not be present, to preserve space - framework must repopulate
+     * it on the client side.
+     */
+    String programName;
+
+    /**
+     * DAB ensemble name (string)
+     */
+    String dabEnsembleName;
+
+    /**
+     * DAB ensemble name abbreviated (string).
+     *
+     * The string must be up to 8 characters long.
+     *
+     * If the short variant is present, the long (DAB_ENSEMBLE_NAME) one must be
+     * present as well.
+     */
+    String dabEnsembleNameShort;
+
+    /**
+     * DAB service name (string)
+     */
+    String dabServiceName;
+
+    /**
+     * DAB service name abbreviated (see DAB_ENSEMBLE_NAME_SHORT) (string)
+     */
+    String dabServiceNameShort;
+
+    /**
+     * DAB component name (string)
+     */
+    String dabComponentName;
+
+    /**
+     * DAB component name abbreviated (see DAB_ENSEMBLE_NAME_SHORT) (string)
+     */
+    String dabComponentNameShort;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramFilter.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramFilter.aidl
new file mode 100644
index 0000000..3dd10eb
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramFilter.aidl
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.ProgramIdentifier;
+
+/**
+ * Large-grain filter to the program list.
+ *
+ * This is meant to reduce binder transaction bandwidth, not for fine-grained
+ * filtering user might expect.
+ *
+ * The filter is designed as conjunctive normal form: the entry that passes the
+ * filter must satisfy all the clauses (members of this struct). Vector clauses
+ * are disjunctions of literals. In other words, there is AND between each
+ * high-level group and OR inside it.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable ProgramFilter {
+    /**
+     * List of identifier types that are filtered by the filter.
+     *
+     * If the program list entry contains at least one identifier of the type
+     * listed, it satisfies this condition.
+     *
+     * Empty list means no filtering on identifier type.
+     */
+    IdentifierType[] identifierTypes;
+
+    /**
+     * List of identifiers that are filtered by the filter.
+     *
+     * If the program list entry contains at least one listed identifier,
+     * it satisfies this condition.
+     *
+     * Empty list means no filtering on identifier.
+     */
+    ProgramIdentifier[] identifiers;
+
+    /**
+     * Includes non-tunable entries that define tree structure on the
+     * program list (i.e. DAB ensembles).
+     */
+    boolean includeCategories;
+
+    /**
+     * Disables updates on entry modifications.
+     *
+     * If {@code true}, 'modified' vector of {@link ProgramListChunk} must contain
+     * list additions only. Once the program is added to the list, it's not
+     * updated anymore.
+     */
+    boolean excludeModifications;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramIdentifier.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramIdentifier.aidl
new file mode 100644
index 0000000..2057d97
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramIdentifier.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.IdentifierType;
+
+/**
+ * A single program identifier component, i.e. frequency or channel ID.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable ProgramIdentifier {
+    /**
+     * Maps to IdentifierType enum.
+     */
+    IdentifierType type = IdentifierType.INVALID;
+
+    /**
+     * The uint64_t value field holds the value in format described in comments
+     * for IdentifierType enum.
+     */
+    long value;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
new file mode 100644
index 0000000..3e2c9cc
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramSelector;
+import android.hardware.broadcastradio.VendorKeyValue;
+
+/**
+ * Program (channel, station) information.
+ *
+ * Carries both user-visible information (like station name) and technical
+ * details (tuning selector).
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable ProgramInfo {
+    /**
+     * Set when the program is currently playing live stream.
+     * This may result in a slightly altered reception parameters,
+     * usually targeted at reduced latency.
+     */
+    const int FLAG_LIVE = 1 << 0;
+
+    /**
+     * Radio stream is not playing, ie. due to bad reception conditions or
+     * buffering. In this state volume knob MAY be disabled to prevent user
+     * increasing volume too much.
+     */
+    const int FLAG_MUTED = 1 << 1;
+
+    /**
+     * Station broadcasts traffic information regularly,
+     * but not necessarily right now.
+     */
+    const int FLAG_TRAFFIC_PROGRAM = 1 << 2;
+
+    /**
+     * Station is broadcasting traffic information at the very moment.
+     */
+    const int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
+
+    /**
+     * Station can be tuned to (not playing static).
+     *
+     * It's the same condition that would stop a seek operation
+     * (i.e. {@link IBroadcastRadio#seek}).
+     *
+     * By definition, this flag must be set for all items on the program list.
+     */
+    const int FLAG_TUNABLE = 1 << 4;
+
+    /**
+     * Audio stream is MONO if this bit is not set.
+     */
+    const int FLAG_STEREO = 1 << 5;
+
+    /**
+     * An identifier used to point at the program (primarily to tune to it).
+     *
+     * This field is required - its type field must not be set to
+     * {@link IdentifierType#INVALID}.
+     */
+    ProgramSelector selector;
+
+    /**
+     * Identifier currently used for program selection.
+     *
+     * It allows to determine which technology is currently used for reception.
+     *
+     * Some program selectors contain tuning information for different radio
+     * technologies (i.e. FM RDS and DAB). For example, user may tune using
+     * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware
+     * may choose to use DAB technology to make actual tuning. This identifier
+     * must reflect that.
+     *
+     * This field is required for currently tuned program only.
+     * For all other items on the program list, its type field must be
+     * initialized to {@link IdentifierType#INVALID}.
+     *
+     * Only primary identifiers for a given radio technology are valid:
+     *  - AMFM_FREQUENCY_KHZ for analog AM/FM;
+     *  - RDS_PI for FM RDS;
+     *  - HD_STATION_ID_EXT;
+     *  - DAB_SID_EXT;
+     *  - DRMO_SERVICE_ID;
+     *  - SXM_SERVICE_ID;
+     *  - VENDOR_*;
+     *  - more might come in next minor versions of this HAL.
+     */
+    ProgramIdentifier logicallyTunedTo;
+
+    /**
+     * Identifier currently used by hardware to physically tune to a channel.
+     *
+     * Some radio technologies broadcast the same program on multiple channels,
+     * i.e. with RDS AF the same program may be broadcasted on multiple
+     * alternative frequencies; the same DAB program may be broadcast on
+     * multiple ensembles. This identifier points to the channel to which the
+     * radio hardware is physically tuned to.
+     *
+     * This field is required for currently tuned program only.
+     * For all other items on the program list, its type field must be
+     * initialized to {@link IdentifierType#INVALID}.
+     *
+     * Only physical identifiers are valid:
+     *  - AMFM_FREQUENCY_KHZ;
+     *  - DAB_ENSEMBLE;
+     *  - DRMO_FREQUENCY_KHZ;
+     *  - SXM_CHANNEL;
+     *  - VENDOR_*;
+     *  - more might come in next minor versions of this HAL.
+     */
+    ProgramIdentifier physicallyTunedTo;
+
+    /**
+     * Primary identifiers of related contents.
+     *
+     * Some radio technologies provide pointers to other programs that carry
+     * related content (i.e. DAB soft-links). This field is a list of pointers
+     * to other programs on the program list.
+     *
+     * This is not a list of programs that carry the same content (i.e.
+     * DAB hard-links, RDS AF). Switching to programs from this list usually
+     * require user action.
+     *
+     * Please note, that these identifiers do not have to exist on the program
+     * list - i.e. DAB tuner may provide information on FM RDS alternatives
+     * despite not supporting FM RDS. If the system has multiple tuners, another
+     * one may have it on its list.
+     *
+     * This field is optional.
+     */
+    @nullable ProgramIdentifier[] relatedContent;
+
+    /**
+     * Program flags.
+     */
+    int infoFlags;
+
+    /**
+     * Signal quality measured in 0% to 100% range to be shown in the UI.
+     */
+    int signalQuality;
+
+    /**
+     * Program metadata (station name, PTY, song title).
+     */
+    Metadata[] metadata;
+
+    /**
+     * Vendor-specific information.
+     *
+     * It may be used for extra features, not supported by the platform,
+     * for example: paid-service=true; bitrate=320kbps.
+     */
+    VendorKeyValue[] vendorInfo;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramListChunk.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramListChunk.aidl
new file mode 100644
index 0000000..a62d461
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramListChunk.aidl
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+
+/**
+ * An update packet of the program list.
+ *
+ * The order of entries in the arrays is unspecified.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable ProgramListChunk {
+    /**
+     * Treats all previously added entries as removed.
+     *
+     * This is meant to save binder transaction bandwidth on 'removed' array
+     * and provide a clear empty state.
+     *
+     * If set, 'removed' array must be null.
+     *
+     * The client may wait with taking action on this until it received the
+     * chunk with complete flag set (to avoid part of stations temporarily
+     * disappearing from the list).
+     */
+    boolean purge;
+
+    /**
+     * If false, it means there are still programs not transmitted,
+     * due for transmission in following updates.
+     *
+     * Used by UIs that wait for complete list instead of displaying
+     * programs while scanning.
+     *
+     * After the whole channel range was scanned and all discovered programs
+     * were transmitted, the last chunk must have set this flag to {@code true}.
+     * This must happen within {@link IBroadcastRadio#LIST_COMPLETE_TIMEOUT_MS}
+     * from the startProgramListUpdates call. If it doesn't, client may assume
+     * the tuner came into a bad state and display error message.
+     */
+    boolean complete;
+
+    /**
+     * Added or modified program list entries.
+     *
+     * Two entries with the same primaryId (ProgramSelector member)
+     * are considered the same.
+     */
+    ProgramInfo[] modified;
+
+    /**
+     * Removed program list entries.
+     *
+     * Contains primaryId (ProgramSelector member) of a program to remove.
+     */
+    @nullable ProgramIdentifier[] removed;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramSelector.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramSelector.aidl
new file mode 100644
index 0000000..8bd3fd4
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramSelector.aidl
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.ProgramIdentifier;
+
+/**
+ * A set of identifiers necessary to tune to a given station.
+ *
+ * This can hold a combination of various identifiers, like:
+ * - AM/FM frequency,
+ * - HD Radio subchannel,
+ * - DAB service ID.
+ *
+ * The type of radio technology is determined by the primary identifier - if the
+ * primary identifier is for DAB, the program is DAB. However, a program of a
+ * specific radio technology may have additional secondary identifiers for other
+ * technologies, i.e. a satellite program may have FM fallback frequency,
+ * if a station broadcasts both via satellite and FM.
+ *
+ * The identifiers from VENDOR_START..VENDOR_END range have limited
+ * serialization capabilities: they are serialized locally, but ignored by the
+ * cloud services. If a program has primary id from vendor range, it's not
+ * synchronized with other devices at all.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable ProgramSelector {
+    /**
+     * Primary program identifier.
+     *
+     * This identifier uniquely identifies a station and can be used for
+     * equality check.
+     *
+     * It can hold only a subset of identifier types, one per each
+     * radio technology:
+     *  - analogue AM/FM: AMFM_FREQUENCY_KHZ;
+     *  - FM RDS: RDS_PI;
+     *  - HD Radio: HD_STATION_ID_EXT;
+     *  - DAB: DAB_SID_EXT;
+     *  - Digital Radio Mondiale: DRMO_SERVICE_ID;
+     *  - SiriusXM: SXM_SERVICE_ID;
+     *  - vendor-specific: VENDOR_START..VENDOR_END.
+     */
+    ProgramIdentifier primaryId;
+
+    /**
+     * Secondary program identifiers.
+     *
+     * These identifiers are supplementary and can speed up tuning process,
+     * but the primary ID must be sufficient (i.e. RDS PI is enough to select
+     * a station from the list after a full band scan).
+     *
+     * Two selectors with different secondary IDs, but the same primary ID are
+     * considered equal. In particular, secondary IDs array may get updated for
+     * an entry on the program list (ie. when a better frequency for a given
+     * station is found).
+     */
+    ProgramIdentifier[] secondaryIds;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Properties.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Properties.aidl
new file mode 100644
index 0000000..36cbaff
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Properties.aidl
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.VendorKeyValue;
+
+/**
+ * Properties of a given broadcast radio module.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable Properties {
+    /**
+     * A company name who made the radio module. Must be a valid, registered
+     * name of the company itself.
+     *
+     * It must be opaque to the Android framework.
+     */
+    String maker;
+
+    /**
+     * A product name. Must be unique within the company.
+     *
+     * It must be opaque to the Android framework.
+     */
+    String product;
+
+    /**
+     * Version of the hardware module.
+     *
+     * It must be opaque to the Android framework.
+     */
+    String version;
+
+    /**
+     * Hardware serial number (for subscription services).
+     *
+     * It must be opaque to the Android framework.
+     */
+    String serial;
+
+    /**
+     * A list of supported {@link IdentifierType} values.
+     *
+     * If an identifier is supported by radio module, it means it can use it for
+     * tuning to ProgramSelector with either primary or secondary Identifier of
+     * a given type.
+     *
+     * Support for VENDOR identifier type does not guarantee compatibility, as
+     * other module properties (implementor, product, version) must be checked.
+     */
+    IdentifierType[] supportedIdentifierTypes;
+
+    /**
+     * Vendor-specific information.
+     *
+     * It may be used for extra features, not supported by the platform,
+     * for example: com.me.preset-slots=6; com.me.ultra-hd-capable={@code false}.
+     */
+    VendorKeyValue[] vendorInfo;
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Result.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Result.aidl
new file mode 100644
index 0000000..9985ccb
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Result.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Result for methods of BroadcastRadio AIDL HAL interfaces.
+ */
+@VintfStability
+@Backing(type="int")
+enum Result {
+    /**
+     * Methods run without error.
+     */
+    OK,
+
+    /**
+     * Internal error in HAL.
+     */
+    INTERNAL_ERROR,
+
+    /**
+     * Error used when the input argument for the method is invalid.
+     */
+    INVALID_ARGUMENTS,
+
+    /**
+     * Error used when the service is of invalid state (i.e. callback
+     * is not registered for IBroadcastRadio).
+     */
+    INVALID_STATE,
+
+    /**
+     * Error used when an operation is not supported.
+     */
+    NOT_SUPPORTED,
+
+    /**
+     * Error used when a tune, seek, step or operation is not completed
+     * within {@link IBroadcastRadio#LIST_COMPLETE_TIMEOUT_MS}.
+     */
+    TIMEOUT,
+
+    /**
+     * Error that does not follow into the error categories above.
+     */
+    UNKNOWN_ERROR,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl
new file mode 100644
index 0000000..c923e92
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * A key-value pair for vendor-specific information to be passed as-is through
+ * Android framework to the front-end application.
+ */
+@VintfStability
+@JavaDerive(equals=true, toString=true)
+parcelable VendorKeyValue {
+    /**
+     * Key must start with unique vendor Java-style namespace,
+     * eg. 'com.somecompany.parameter1'.
+     */
+    String key;
+
+    /**
+     * Value must be passed through the framework without any changes.
+     * Format of this string can vary across vendors.
+     */
+    String value;
+}
diff --git a/broadcastradio/aidl/default/Android.bp b/broadcastradio/aidl/default/Android.bp
new file mode 100644
index 0000000..720aa8a
--- /dev/null
+++ b/broadcastradio/aidl/default/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_binary {
+    name: "android.hardware.broadcastradio-service.default",
+    relative_install_path: "hw",
+    init_rc: ["broadcastradio-default.rc"],
+    vintf_fragments: ["broadcastradio-default.xml"],
+    vendor: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    srcs: [
+        "BroadcastRadio.cpp",
+        "main.cpp",
+        "VirtualProgram.cpp",
+        "VirtualRadio.cpp",
+    ],
+    static_libs: [
+        "android.hardware.broadcastradio@common-utils-aidl-lib",
+        "android.hardware.broadcastradio@common-utils-lib",
+    ],
+    shared_libs: [
+        "android.hardware.broadcastradio-V1-ndk",
+        "libbase",
+        "libbinder_ndk",
+        "liblog",
+        "libcutils",
+    ],
+}
diff --git a/broadcastradio/aidl/default/BroadcastRadio.cpp b/broadcastradio/aidl/default/BroadcastRadio.cpp
new file mode 100644
index 0000000..0209a0e
--- /dev/null
+++ b/broadcastradio/aidl/default/BroadcastRadio.cpp
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BroadcastRadio.h"
+#include <broadcastradio-utils-aidl/Utils.h>
+#include "resources.h"
+
+#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
+#include <aidl/android/hardware/broadcastradio/Result.h>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include <private/android_filesystem_config.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+using ::aidl::android::hardware::broadcastradio::utils::resultToInt;
+using ::aidl::android::hardware::broadcastradio::utils::tunesTo;
+using ::android::base::EqualsIgnoreCase;
+using ::ndk::ScopedAStatus;
+using ::std::literals::chrono_literals::operator""ms;
+using ::std::literals::chrono_literals::operator""s;
+using ::std::lock_guard;
+using ::std::mutex;
+using ::std::string;
+using ::std::vector;
+
+namespace {
+
+inline constexpr std::chrono::milliseconds kSeekDelayTimeMs = 200ms;
+inline constexpr std::chrono::milliseconds kStepDelayTimeMs = 100ms;
+inline constexpr std::chrono::milliseconds kTuneDelayTimeMs = 150ms;
+inline constexpr std::chrono::seconds kListDelayTimeS = 1s;
+
+// clang-format off
+const AmFmRegionConfig kDefaultAmFmConfig = {
+        {
+                {87500, 108000, 100, 100},  // FM
+                {153, 282, 3, 9},           // AM LW
+                {531, 1620, 9, 9},          // AM MW
+                {1600, 30000, 1, 5},        // AM SW
+        },
+        AmFmRegionConfig::DEEMPHASIS_D50,
+        AmFmRegionConfig::RDS};
+// clang-format on
+
+Properties initProperties(const VirtualRadio& virtualRadio) {
+    Properties prop = {};
+
+    prop.maker = "Android";
+    prop.product = virtualRadio.getName();
+    prop.supportedIdentifierTypes = vector<IdentifierType>({
+            IdentifierType::AMFM_FREQUENCY_KHZ,
+            IdentifierType::RDS_PI,
+            IdentifierType::HD_STATION_ID_EXT,
+            IdentifierType::DAB_SID_EXT,
+    });
+    prop.vendorInfo = vector<VendorKeyValue>({
+            {"com.android.sample", "sample"},
+    });
+
+    return prop;
+}
+
+// Makes ProgramInfo that does not point to any particular program
+ProgramInfo makeSampleProgramInfo(const ProgramSelector& selector) {
+    ProgramInfo info = {};
+    info.selector = selector;
+    info.logicallyTunedTo =
+            utils::makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ,
+                                  utils::getId(selector, IdentifierType::AMFM_FREQUENCY_KHZ));
+    info.physicallyTunedTo = info.logicallyTunedTo;
+    return info;
+}
+
+static bool checkDumpCallerHasWritePermissions(int fd) {
+    uid_t uid = AIBinder_getCallingUid();
+    if (uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM) {
+        return true;
+    }
+    dprintf(fd, "BroadcastRadio HAL dump must be root, shell or system\n");
+    return false;
+}
+
+}  // namespace
+
+BroadcastRadio::BroadcastRadio(const VirtualRadio& virtualRadio)
+    : mVirtualRadio(virtualRadio),
+      mAmFmConfig(kDefaultAmFmConfig),
+      mProperties(initProperties(virtualRadio)) {
+    const auto& ranges = kDefaultAmFmConfig.ranges;
+    if (ranges.size() > 0) {
+        ProgramSelector sel = utils::makeSelectorAmfm(ranges[0].lowerBound);
+        VirtualProgram virtualProgram = {};
+        if (mVirtualRadio.getProgram(sel, &virtualProgram)) {
+            mCurrentProgram = virtualProgram.selector;
+        } else {
+            mCurrentProgram = sel;
+        }
+    }
+}
+
+BroadcastRadio::~BroadcastRadio() {
+    mThread.reset();
+}
+
+ScopedAStatus BroadcastRadio::getAmFmRegionConfig(bool full, AmFmRegionConfig* returnConfigs) {
+    if (full) {
+        *returnConfigs = {};
+        returnConfigs->ranges = vector<AmFmBandRange>({
+                {65000, 108000, 10, 0},  // FM
+                {150, 30000, 1, 0},      // AM
+        });
+        returnConfigs->fmDeemphasis =
+                AmFmRegionConfig::DEEMPHASIS_D50 | AmFmRegionConfig::DEEMPHASIS_D75;
+        returnConfigs->fmRds = AmFmRegionConfig::RDS | AmFmRegionConfig::RBDS;
+        return ScopedAStatus::ok();
+    }
+    lock_guard<mutex> lk(mMutex);
+    *returnConfigs = mAmFmConfig;
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::getDabRegionConfig(vector<DabTableEntry>* returnConfigs) {
+    *returnConfigs = {
+            {"5A", 174928},  {"7D", 194064},  {"8A", 195936},  {"8B", 197648},  {"9A", 202928},
+            {"9B", 204640},  {"9C", 206352},  {"10B", 211648}, {"10C", 213360}, {"10D", 215072},
+            {"11A", 216928}, {"11B", 218640}, {"11C", 220352}, {"11D", 222064}, {"12A", 223936},
+            {"12B", 225648}, {"12C", 227360}, {"12D", 229072},
+    };
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::getImage(int32_t id, vector<uint8_t>* returnImage) {
+    LOG(DEBUG) << __func__ << ": fetching image " << std::hex << id;
+
+    if (id == resources::kDemoPngId) {
+        *returnImage = vector<uint8_t>(resources::kDemoPng, std::end(resources::kDemoPng));
+        return ScopedAStatus::ok();
+    }
+
+    LOG(WARNING) << __func__ << ": image of id " << std::hex << id << " doesn't exist";
+    *returnImage = {};
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::getProperties(Properties* returnProperties) {
+    lock_guard<mutex> lk(mMutex);
+    *returnProperties = mProperties;
+    return ScopedAStatus::ok();
+}
+
+ProgramInfo BroadcastRadio::tuneInternalLocked(const ProgramSelector& sel) {
+    LOG(DEBUG) << __func__ << ": tune (internal) to " << sel.toString();
+
+    VirtualProgram virtualProgram = {};
+    ProgramInfo programInfo;
+    if (mVirtualRadio.getProgram(sel, &virtualProgram)) {
+        mCurrentProgram = virtualProgram.selector;
+        programInfo = virtualProgram;
+    } else {
+        mCurrentProgram = sel;
+        programInfo = makeSampleProgramInfo(sel);
+    }
+    mIsTuneCompleted = true;
+
+    return programInfo;
+}
+
+ScopedAStatus BroadcastRadio::setTunerCallback(const std::shared_ptr<ITunerCallback>& callback) {
+    LOG(DEBUG) << __func__ << ": setTunerCallback";
+
+    if (callback == nullptr) {
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INVALID_ARGUMENTS), "cannot set tuner callback to null");
+    }
+
+    lock_guard<mutex> lk(mMutex);
+    mCallback = callback;
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::unsetTunerCallback() {
+    LOG(DEBUG) << __func__ << ": unsetTunerCallback";
+
+    lock_guard<mutex> lk(mMutex);
+    mCallback = nullptr;
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::tune(const ProgramSelector& program) {
+    LOG(DEBUG) << __func__ << ": tune to " << program.toString() << "...";
+
+    lock_guard<mutex> lk(mMutex);
+    if (mCallback == nullptr) {
+        LOG(ERROR) << __func__ << ": callback is not registered.";
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INVALID_STATE), "callback is not registered");
+    }
+
+    if (!utils::isSupported(mProperties, program)) {
+        LOG(WARNING) << __func__ << ": selector not supported: " << program.toString();
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::NOT_SUPPORTED), "selector is not supported");
+    }
+
+    if (!utils::isValid(program)) {
+        LOG(ERROR) << __func__ << ": selector is not valid: " << program.toString();
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INVALID_ARGUMENTS), "selector is not valid");
+    }
+
+    cancelLocked();
+
+    mIsTuneCompleted = false;
+    std::shared_ptr<ITunerCallback> callback = mCallback;
+    auto task = [this, program, callback]() {
+        ProgramInfo programInfo = {};
+        {
+            lock_guard<mutex> lk(mMutex);
+            programInfo = tuneInternalLocked(program);
+        }
+        callback->onCurrentProgramInfoChanged(programInfo);
+    };
+    mThread->schedule(task, kTuneDelayTimeMs);
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::seek(bool directionUp, bool skipSubChannel) {
+    LOG(DEBUG) << __func__ << ": seek " << (directionUp ? "up" : "down") << " with skipSubChannel? "
+               << (skipSubChannel ? "yes" : "no") << "...";
+
+    lock_guard<mutex> lk(mMutex);
+    if (mCallback == nullptr) {
+        LOG(ERROR) << __func__ << ": callback is not registered.";
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INVALID_STATE), "callback is not registered");
+    }
+
+    cancelLocked();
+
+    const auto& list = mVirtualRadio.getProgramList();
+    std::shared_ptr<ITunerCallback> callback = mCallback;
+    if (list.empty()) {
+        mIsTuneCompleted = false;
+        auto task = [callback]() {
+            LOG(DEBUG) << "seek: program list is empty, seek couldn't stop";
+
+            callback->onTuneFailed(Result::TIMEOUT, {});
+        };
+        mThread->schedule(task, kSeekDelayTimeMs);
+
+        return ScopedAStatus::ok();
+    }
+
+    // The list is not sorted here since it has already stored in VirtualRadio.
+    // If the list is not sorted in advance, it should be sorted here.
+    const auto& current = mCurrentProgram;
+    auto found = std::lower_bound(list.begin(), list.end(), VirtualProgram({current}));
+    if (directionUp) {
+        if (found < list.end() - 1) {
+            if (tunesTo(current, found->selector)) found++;
+        } else {
+            found = list.begin();
+        }
+    } else {
+        if (found > list.begin() && found != list.end()) {
+            found--;
+        } else {
+            found = list.end() - 1;
+        }
+    }
+    const ProgramSelector tuneTo = found->selector;
+
+    mIsTuneCompleted = false;
+    auto task = [this, tuneTo, callback]() {
+        ProgramInfo programInfo = {};
+        {
+            lock_guard<mutex> lk(mMutex);
+            programInfo = tuneInternalLocked(tuneTo);
+        }
+        callback->onCurrentProgramInfoChanged(programInfo);
+    };
+    mThread->schedule(task, kSeekDelayTimeMs);
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::step(bool directionUp) {
+    LOG(DEBUG) << __func__ << ": step " << (directionUp ? "up" : "down") << "...";
+
+    lock_guard<mutex> lk(mMutex);
+    if (mCallback == nullptr) {
+        LOG(ERROR) << __func__ << ": callback is not registered.";
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INVALID_STATE), "callback is not registered");
+    }
+
+    cancelLocked();
+
+    if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ)) {
+        LOG(WARNING) << __func__ << ": can't step in anything else than AM/FM";
+        return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::NOT_SUPPORTED), "cannot step in anything else than AM/FM");
+    }
+
+    int64_t stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ);
+    std::optional<AmFmBandRange> range = getAmFmRangeLocked();
+    if (!range) {
+        LOG(ERROR) << __func__ << ": can't find current band or tune operation is in process";
+        ScopedAStatus::fromServiceSpecificErrorWithMessage(
+                resultToInt(Result::INTERNAL_ERROR),
+                "can't find current band or tune operation is in process");
+    }
+
+    if (directionUp) {
+        stepTo += range->spacing;
+    } else {
+        stepTo -= range->spacing;
+    }
+    if (stepTo > range->upperBound) {
+        stepTo = range->lowerBound;
+    }
+    if (stepTo < range->lowerBound) {
+        stepTo = range->upperBound;
+    }
+
+    mIsTuneCompleted = false;
+    std::shared_ptr<ITunerCallback> callback = mCallback;
+    auto task = [this, stepTo, callback]() {
+        ProgramInfo programInfo;
+        {
+            lock_guard<mutex> lk(mMutex);
+            programInfo = tuneInternalLocked(utils::makeSelectorAmfm(stepTo));
+        }
+        callback->onCurrentProgramInfoChanged(programInfo);
+    };
+    mThread->schedule(task, kStepDelayTimeMs);
+
+    return ScopedAStatus::ok();
+}
+
+void BroadcastRadio::cancelLocked() {
+    LOG(DEBUG) << __func__ << ": cancelling current operations...";
+
+    mThread->cancelAll();
+    if (mCurrentProgram.primaryId.type != IdentifierType::INVALID) {
+        mIsTuneCompleted = true;
+    }
+}
+
+ScopedAStatus BroadcastRadio::cancel() {
+    LOG(DEBUG) << __func__ << ": cancel pending tune, seek and step...";
+
+    lock_guard<mutex> lk(mMutex);
+    cancelLocked();
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::startProgramListUpdates(const ProgramFilter& filter) {
+    LOG(DEBUG) << __func__ << ": requested program list updates, filter = " << filter.toString()
+               << "...";
+
+    auto filterCb = [&filter](const VirtualProgram& program) {
+        return utils::satisfies(filter, program.selector);
+    };
+
+    lock_guard<mutex> lk(mMutex);
+
+    const auto& list = mVirtualRadio.getProgramList();
+    vector<VirtualProgram> filteredList;
+    std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb);
+
+    auto task = [this, filteredList]() {
+        std::shared_ptr<ITunerCallback> callback;
+        {
+            lock_guard<mutex> lk(mMutex);
+            if (mCallback == nullptr) {
+                LOG(WARNING) << "Callback is null when updating program List";
+                return;
+            }
+            callback = mCallback;
+        }
+
+        ProgramListChunk chunk = {};
+        chunk.purge = true;
+        chunk.complete = true;
+        chunk.modified = vector<ProgramInfo>(filteredList.begin(), filteredList.end());
+
+        callback->onProgramListUpdated(chunk);
+    };
+    mThread->schedule(task, kListDelayTimeS);
+
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::stopProgramListUpdates() {
+    LOG(DEBUG) << __func__ << ": requested program list updates to stop...";
+    // TODO(b/243681584) Implement stop program list updates method
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::isConfigFlagSet(ConfigFlag flag, [[maybe_unused]] bool* returnIsSet) {
+    LOG(DEBUG) << __func__ << ": flag = " << toString(flag);
+
+    LOG(INFO) << __func__ << ": getting ConfigFlag is not supported";
+    return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            resultToInt(Result::NOT_SUPPORTED), "getting ConfigFlag is not supported");
+}
+
+ScopedAStatus BroadcastRadio::setConfigFlag(ConfigFlag flag, bool value) {
+    LOG(DEBUG) << __func__ << ": flag = " << toString(flag) << ", value = " << value;
+
+    LOG(INFO) << __func__ << ": setting ConfigFlag is not supported";
+    return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            resultToInt(Result::NOT_SUPPORTED), "setting ConfigFlag is not supported");
+}
+
+ScopedAStatus BroadcastRadio::setParameters(
+        [[maybe_unused]] const vector<VendorKeyValue>& parameters,
+        vector<VendorKeyValue>* returnParameters) {
+    // TODO(b/243682330) Support vendor parameter functionality
+    *returnParameters = {};
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus BroadcastRadio::getParameters([[maybe_unused]] const vector<string>& keys,
+                                            vector<VendorKeyValue>* returnParameters) {
+    // TODO(b/243682330) Support vendor parameter functionality
+    *returnParameters = {};
+    return ScopedAStatus::ok();
+}
+
+std::optional<AmFmBandRange> BroadcastRadio::getAmFmRangeLocked() const {
+    if (!mIsTuneCompleted) {
+        LOG(WARNING) << __func__ << ": tune operation is in process";
+        return {};
+    }
+    if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ)) {
+        LOG(WARNING) << __func__ << ": current program does not has AMFM_FREQUENCY_KHZ identifier";
+        return {};
+    }
+
+    int64_t freq = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ);
+    for (const auto& range : mAmFmConfig.ranges) {
+        if (range.lowerBound <= freq && range.upperBound >= freq) {
+            return range;
+        }
+    }
+
+    return {};
+}
+
+ScopedAStatus BroadcastRadio::registerAnnouncementListener(
+        [[maybe_unused]] const std::shared_ptr<IAnnouncementListener>& listener,
+        const vector<AnnouncementType>& enabled, std::shared_ptr<ICloseHandle>* returnCloseHandle) {
+    LOG(DEBUG) << __func__ << ": registering announcement listener for "
+               << utils::vectorToString(enabled);
+
+    // TODO(b/243683842) Support announcement listener
+    *returnCloseHandle = nullptr;
+    LOG(INFO) << __func__ << ": registering announcementListener is not supported";
+    return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+            resultToInt(Result::NOT_SUPPORTED),
+            "registering announcementListener is not supported");
+}
+
+binder_status_t BroadcastRadio::dump(int fd, const char** args, uint32_t numArgs) {
+    if (numArgs == 0) {
+        return dumpsys(fd);
+    }
+
+    string option = string(args[0]);
+    if (EqualsIgnoreCase(option, "--help")) {
+        return cmdHelp(fd);
+    } else if (EqualsIgnoreCase(option, "--tune")) {
+        return cmdTune(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--seek")) {
+        return cmdSeek(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--step")) {
+        return cmdStep(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--cancel")) {
+        return cmdCancel(fd, numArgs);
+    } else if (EqualsIgnoreCase(option, "--startProgramListUpdates")) {
+        return cmdStartProgramListUpdates(fd, args, numArgs);
+    } else if (EqualsIgnoreCase(option, "--stopProgramListUpdates")) {
+        return cmdStopProgramListUpdates(fd, numArgs);
+    }
+    dprintf(fd, "Invalid option: %s\n", option.c_str());
+    return STATUS_BAD_VALUE;
+}
+
+binder_status_t BroadcastRadio::dumpsys(int fd) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    lock_guard<mutex> lk(mMutex);
+    dprintf(fd, "AmFmRegionConfig: %s\n", mAmFmConfig.toString().c_str());
+    dprintf(fd, "Properties: %s \n", mProperties.toString().c_str());
+    if (mIsTuneCompleted) {
+        dprintf(fd, "Tune completed\n");
+    } else {
+        dprintf(fd, "Tune not completed\n");
+    }
+    if (mCallback == nullptr) {
+        dprintf(fd, "No ITunerCallback registered\n");
+    } else {
+        dprintf(fd, "ITunerCallback registered\n");
+    }
+    dprintf(fd, "CurrentProgram: %s \n", mCurrentProgram.toString().c_str());
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdHelp(int fd) const {
+    dprintf(fd, "Usage: \n\n");
+    dprintf(fd, "[no args]: dumps focus listener / gain callback registered status\n");
+    dprintf(fd, "--help: shows this help\n");
+    dprintf(fd,
+            "--tune amfm <FREQUENCY>: tunes amfm radio to frequency (in Hz) specified: "
+            "frequency (int) \n"
+            "--tune dab <SID> <ENSEMBLE>: tunes dab radio to sid and ensemble specified: "
+            "sidExt (int), ensemble (int) \n");
+    dprintf(fd,
+            "--seek [up|down] <SKIP_SUB_CHANNEL>: seek with direction (up or down) and "
+            "option whether skipping sub channel: "
+            "skipSubChannel (string, should be either \"true\" or \"false\")\n");
+    dprintf(fd, "--step [up|down]: step in direction (up or down) specified\n");
+    dprintf(fd, "--cancel: cancel current pending tune, step, and seek\n");
+    dprintf(fd,
+            "--startProgramListUpdates <IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> "
+            "<EXCLUDE_MODIFICATIONS>: start update program list with the filter specified: "
+            "identifier types (string, in format <TYPE>,<TYPE>,...,<TYPE> or \"null\" (if empty), "
+            "where TYPE is int), "
+            "program identifiers (string, in format "
+            "<TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE> or \"null\" (if empty), "
+            "where TYPE is int and VALUE is long), "
+            "includeCategories (string, should be either \"true\" or \"false\"), "
+            "excludeModifications (string, should be either \"true\" or \"false\")\n");
+    dprintf(fd, "--stopProgramListUpdates: stop current pending program list updates\n");
+    dprintf(fd,
+            "Note on <TYPE> for --startProgramList command: it is int for identifier type. "
+            "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl "
+            "for its definition.\n");
+    dprintf(fd,
+            "Note on <VALUE> for --startProgramList command: it is long type for identifier value. "
+            "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl "
+            "for its value.\n");
+
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdTune(int fd, const char** args, uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 3 && numArgs != 4) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide --tune amfm <FREQUENCY> "
+                "or --tune dab <SID> <ENSEMBLE>\n");
+        return STATUS_BAD_VALUE;
+    }
+    bool isDab = false;
+    if (EqualsIgnoreCase(string(args[1]), "dab")) {
+        isDab = true;
+    } else if (!EqualsIgnoreCase(string(args[1]), "amfm")) {
+        dprintf(fd, "Unknown radio type provided with tune: %s\n", args[1]);
+        return STATUS_BAD_VALUE;
+    }
+    ProgramSelector sel = {};
+    if (isDab) {
+        if (numArgs != 4) {
+            dprintf(fd,
+                    "Invalid number of arguments: please provide --tune dab <SID> <ENSEMBLE>\n");
+            return STATUS_BAD_VALUE;
+        }
+        int sid;
+        if (!utils::parseArgInt(string(args[2]), &sid)) {
+            dprintf(fd, "Non-integer sid provided with tune: %s\n", string(args[2]).c_str());
+            return STATUS_BAD_VALUE;
+        }
+        int ensemble;
+        if (!utils::parseArgInt(string(args[3]), &ensemble)) {
+            dprintf(fd, "Non-integer ensemble provided with tune: %s\n", string(args[3]).c_str());
+            return STATUS_BAD_VALUE;
+        }
+        sel = utils::makeSelectorDab(sid, ensemble);
+    } else {
+        if (numArgs != 3) {
+            dprintf(fd, "Invalid number of arguments: please provide --tune amfm <FREQUENCY>\n");
+            return STATUS_BAD_VALUE;
+        }
+        int freq;
+        if (!utils::parseArgInt(string(args[2]), &freq)) {
+            dprintf(fd, "Non-integer frequency provided with tune: %s\n", string(args[2]).c_str());
+            return STATUS_BAD_VALUE;
+        }
+        sel = utils::makeSelectorAmfm(freq);
+    }
+
+    auto tuneResult = tune(sel);
+    if (!tuneResult.isOk()) {
+        dprintf(fd, "Unable to tune %s radio to %s\n", args[1], sel.toString().c_str());
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Tune %s radio to %s \n", args[1], sel.toString().c_str());
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdSeek(int fd, const char** args, uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 3) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide --seek <DIRECTION> "
+                "<SKIP_SUB_CHANNEL>\n");
+        return STATUS_BAD_VALUE;
+    }
+    string seekDirectionIn = string(args[1]);
+    bool seekDirectionUp;
+    if (!utils::parseArgDirection(seekDirectionIn, &seekDirectionUp)) {
+        dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with seek: %s\n",
+                seekDirectionIn.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    string skipSubChannelIn = string(args[2]);
+    bool skipSubChannel;
+    if (!utils::parseArgBool(skipSubChannelIn, &skipSubChannel)) {
+        dprintf(fd, "Invalid skipSubChannel (\"true\" or \"false\") provided with seek: %s\n",
+                skipSubChannelIn.c_str());
+        return STATUS_BAD_VALUE;
+    }
+
+    auto seekResult = seek(seekDirectionUp, skipSubChannel);
+    if (!seekResult.isOk()) {
+        dprintf(fd, "Unable to seek in %s direction\n", seekDirectionIn.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Seek in %s direction\n", seekDirectionIn.c_str());
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdStep(int fd, const char** args, uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 2) {
+        dprintf(fd, "Invalid number of arguments: please provide --step <DIRECTION>\n");
+        return STATUS_BAD_VALUE;
+    }
+    string stepDirectionIn = string(args[1]);
+    bool stepDirectionUp;
+    if (!utils::parseArgDirection(stepDirectionIn, &stepDirectionUp)) {
+        dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with step: %s\n",
+                stepDirectionIn.c_str());
+        return STATUS_BAD_VALUE;
+    }
+
+    auto stepResult = step(stepDirectionUp);
+    if (!stepResult.isOk()) {
+        dprintf(fd, "Unable to step in %s direction\n", stepDirectionIn.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Step in %s direction\n", stepDirectionIn.c_str());
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdCancel(int fd, uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 1) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide --cancel "
+                "only and no more arguments\n");
+        return STATUS_BAD_VALUE;
+    }
+
+    auto cancelResult = cancel();
+    if (!cancelResult.isOk()) {
+        dprintf(fd, "Unable to cancel pending tune, seek, and step\n");
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Canceled pending tune, seek, and step\n");
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdStartProgramListUpdates(int fd, const char** args,
+                                                           uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 5) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide --startProgramListUpdates "
+                "<IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> "
+                "<EXCLUDE_MODIFICATIONS>\n");
+        return STATUS_BAD_VALUE;
+    }
+    string filterTypesStr = string(args[1]);
+    std::vector<IdentifierType> filterTypeList;
+    if (!EqualsIgnoreCase(filterTypesStr, "null") &&
+        !utils::parseArgIdentifierTypeArray(filterTypesStr, &filterTypeList)) {
+        dprintf(fd,
+                "Invalid identifier types provided with startProgramListUpdates: %s, "
+                "should be: <TYPE>,<TYPE>,...,<TYPE>\n",
+                filterTypesStr.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    string filtersStr = string(args[2]);
+    std::vector<ProgramIdentifier> filterList;
+    if (!EqualsIgnoreCase(filtersStr, "null") &&
+        !utils::parseProgramIdentifierList(filtersStr, &filterList)) {
+        dprintf(fd,
+                "Invalid program identifiers provided with startProgramListUpdates: %s, "
+                "should be: <TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE>\n",
+                filtersStr.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    string includeCategoriesStr = string(args[3]);
+    bool includeCategories;
+    if (!utils::parseArgBool(includeCategoriesStr, &includeCategories)) {
+        dprintf(fd,
+                "Invalid includeCategories (\"true\" or \"false\") "
+                "provided with startProgramListUpdates : %s\n",
+                includeCategoriesStr.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    string excludeModificationsStr = string(args[4]);
+    bool excludeModifications;
+    if (!utils::parseArgBool(excludeModificationsStr, &excludeModifications)) {
+        dprintf(fd,
+                "Invalid excludeModifications(\"true\" or \"false\") "
+                "provided with startProgramListUpdates : %s\n",
+                excludeModificationsStr.c_str());
+        return STATUS_BAD_VALUE;
+    }
+    ProgramFilter filter = {filterTypeList, filterList, includeCategories, excludeModifications};
+
+    auto updateResult = startProgramListUpdates(filter);
+    if (!updateResult.isOk()) {
+        dprintf(fd, "Unable to start program list update for filter %s \n",
+                filter.toString().c_str());
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Start program list update for filter %s\n", filter.toString().c_str());
+    return STATUS_OK;
+}
+
+binder_status_t BroadcastRadio::cmdStopProgramListUpdates(int fd, uint32_t numArgs) {
+    if (!checkDumpCallerHasWritePermissions(fd)) {
+        return STATUS_PERMISSION_DENIED;
+    }
+    if (numArgs != 1) {
+        dprintf(fd,
+                "Invalid number of arguments: please provide --stopProgramListUpdates "
+                "only and no more arguments\n");
+        return STATUS_BAD_VALUE;
+    }
+
+    auto stopResult = stopProgramListUpdates();
+    if (!stopResult.isOk()) {
+        dprintf(fd, "Unable to stop pending program list update\n");
+        return STATUS_BAD_VALUE;
+    }
+    dprintf(fd, "Stop pending program list update\n");
+    return STATUS_OK;
+}
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/BroadcastRadio.h b/broadcastradio/aidl/default/BroadcastRadio.h
new file mode 100644
index 0000000..1c85ddc
--- /dev/null
+++ b/broadcastradio/aidl/default/BroadcastRadio.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "VirtualRadio.h"
+
+#include <aidl/android/hardware/broadcastradio/AmFmBandRange.h>
+#include <aidl/android/hardware/broadcastradio/AmFmRegionConfig.h>
+#include <aidl/android/hardware/broadcastradio/AnnouncementType.h>
+#include <aidl/android/hardware/broadcastradio/BnBroadcastRadio.h>
+#include <aidl/android/hardware/broadcastradio/DabTableEntry.h>
+#include <aidl/android/hardware/broadcastradio/IAnnouncementListener.h>
+#include <aidl/android/hardware/broadcastradio/ICloseHandle.h>
+#include <aidl/android/hardware/broadcastradio/ITunerCallback.h>
+#include <aidl/android/hardware/broadcastradio/Properties.h>
+#include <broadcastradio-utils/WorkerThread.h>
+
+#include <android-base/thread_annotations.h>
+
+#include <optional>
+
+namespace aidl::android::hardware::broadcastradio {
+
+class BroadcastRadio final : public BnBroadcastRadio {
+  public:
+    explicit BroadcastRadio(const VirtualRadio& virtualRadio);
+    ~BroadcastRadio();
+    ndk::ScopedAStatus getAmFmRegionConfig(bool full, AmFmRegionConfig* returnConfigs) override;
+    ndk::ScopedAStatus getDabRegionConfig(std::vector<DabTableEntry>* returnConfigs) override;
+    ndk::ScopedAStatus getImage(int32_t id, std::vector<uint8_t>* returnImage) override;
+    ndk::ScopedAStatus getProperties(Properties* returnProperties) override;
+
+    ndk::ScopedAStatus setTunerCallback(const std::shared_ptr<ITunerCallback>& callback) override;
+    ndk::ScopedAStatus unsetTunerCallback() override;
+    ndk::ScopedAStatus tune(const ProgramSelector& program) override;
+    ndk::ScopedAStatus seek(bool directionUp, bool skipSubChannel) override;
+    ndk::ScopedAStatus step(bool directionUp) override;
+    ndk::ScopedAStatus cancel() override;
+    ndk::ScopedAStatus startProgramListUpdates(const ProgramFilter& filter) override;
+    ndk::ScopedAStatus stopProgramListUpdates() override;
+    ndk::ScopedAStatus isConfigFlagSet(ConfigFlag flag, bool* returnIsSet) override;
+    ndk::ScopedAStatus setConfigFlag(ConfigFlag flag, bool in_value) override;
+    ndk::ScopedAStatus setParameters(const std::vector<VendorKeyValue>& parameters,
+                                     std::vector<VendorKeyValue>* returnParameters) override;
+    ndk::ScopedAStatus getParameters(const std::vector<std::string>& keys,
+                                     std::vector<VendorKeyValue>* returnParameters) override;
+    ndk::ScopedAStatus registerAnnouncementListener(
+            const std::shared_ptr<IAnnouncementListener>& listener,
+            const std::vector<AnnouncementType>& enabled,
+            std::shared_ptr<ICloseHandle>* returnCloseHandle) override;
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+  private:
+    const VirtualRadio& mVirtualRadio;
+    std::mutex mMutex;
+    AmFmRegionConfig mAmFmConfig GUARDED_BY(mMutex);
+    std::unique_ptr<::android::WorkerThread> mThread GUARDED_BY(mMutex) =
+            std::unique_ptr<::android::WorkerThread>(new ::android::WorkerThread());
+    bool mIsTuneCompleted GUARDED_BY(mMutex) = true;
+    Properties mProperties GUARDED_BY(mMutex);
+    ProgramSelector mCurrentProgram GUARDED_BY(mMutex) = {};
+    std::shared_ptr<ITunerCallback> mCallback GUARDED_BY(mMutex);
+
+    std::optional<AmFmBandRange> getAmFmRangeLocked() const;
+    void cancelLocked();
+    ProgramInfo tuneInternalLocked(const ProgramSelector& sel);
+
+    binder_status_t cmdHelp(int fd) const;
+    binder_status_t cmdTune(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdSeek(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdStep(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdCancel(int fd, uint32_t numArgs);
+    binder_status_t cmdStartProgramListUpdates(int fd, const char** args, uint32_t numArgs);
+    binder_status_t cmdStopProgramListUpdates(int fd, uint32_t numArgs);
+
+    binder_status_t dumpsys(int fd);
+};
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/VirtualProgram.cpp b/broadcastradio/aidl/default/VirtualProgram.cpp
new file mode 100644
index 0000000..0df0a82
--- /dev/null
+++ b/broadcastradio/aidl/default/VirtualProgram.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtualProgram.h"
+
+#include <broadcastradio-utils-aidl/Utils.h>
+#include "resources.h"
+
+#include <android-base/logging.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+using ::std::vector;
+
+VirtualProgram::operator ProgramInfo() const {
+    ProgramInfo info = {};
+
+    info.selector = selector;
+
+    IdentifierType programType = selector.primaryId.type;
+    bool isDigital = (programType != IdentifierType::AMFM_FREQUENCY_KHZ &&
+                      programType != IdentifierType::RDS_PI);
+
+    auto selectId = [&info](const IdentifierType& type) {
+        return utils::makeIdentifier(type, utils::getId(info.selector, type));
+    };
+
+    switch (programType) {
+        case IdentifierType::AMFM_FREQUENCY_KHZ:
+            info.logicallyTunedTo = info.physicallyTunedTo =
+                    selectId(IdentifierType::AMFM_FREQUENCY_KHZ);
+            break;
+        case IdentifierType::RDS_PI:
+            info.logicallyTunedTo = selectId(IdentifierType::RDS_PI);
+            info.physicallyTunedTo = selectId(IdentifierType::AMFM_FREQUENCY_KHZ);
+            break;
+        case IdentifierType::HD_STATION_ID_EXT:
+            info.logicallyTunedTo = selectId(IdentifierType::HD_STATION_ID_EXT);
+            info.physicallyTunedTo = selectId(IdentifierType::AMFM_FREQUENCY_KHZ);
+            break;
+        case IdentifierType::DAB_SID_EXT:
+            info.logicallyTunedTo = selectId(IdentifierType::DAB_SID_EXT);
+            info.physicallyTunedTo = selectId(IdentifierType::DAB_ENSEMBLE);
+            break;
+        case IdentifierType::DRMO_SERVICE_ID:
+            info.logicallyTunedTo = selectId(IdentifierType::DRMO_SERVICE_ID);
+            info.physicallyTunedTo = selectId(IdentifierType::DRMO_FREQUENCY_KHZ);
+            break;
+        case IdentifierType::SXM_SERVICE_ID:
+            info.logicallyTunedTo = selectId(IdentifierType::SXM_SERVICE_ID);
+            info.physicallyTunedTo = selectId(IdentifierType::SXM_CHANNEL);
+            break;
+        default:
+            LOG(FATAL) << "unsupported program type: " << toString(programType);
+            return {};
+    }
+
+    info.infoFlags |= (ProgramInfo::FLAG_TUNABLE | ProgramInfo::FLAG_STEREO);
+    info.signalQuality = isDigital ? kSignalQualityDigital : kSignalQualityNonDigital;
+
+    info.metadata = vector<Metadata>({
+            Metadata::make<Metadata::rdsPs>(programName),
+            Metadata::make<Metadata::songTitle>(songTitle),
+            Metadata::make<Metadata::songArtist>(songArtist),
+            Metadata::make<Metadata::stationIcon>(resources::kDemoPngId),
+            Metadata::make<Metadata::albumArt>(resources::kDemoPngId),
+    });
+
+    info.vendorInfo = vector<VendorKeyValue>({
+            {"com.android.sample", "sample"},
+            {"com.android.sample.VirtualProgram", "VirtualProgram"},
+    });
+
+    return info;
+}
+
+bool operator<(const VirtualProgram& lhs, const VirtualProgram& rhs) {
+    auto& l = lhs.selector;
+    auto& r = rhs.selector;
+
+    // Two programs with the same primaryId are considered the same.
+    if (l.primaryId.type != r.primaryId.type) return l.primaryId.type < r.primaryId.type;
+
+    return l.primaryId.value < r.primaryId.value;
+}
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/VirtualProgram.h b/broadcastradio/aidl/default/VirtualProgram.h
new file mode 100644
index 0000000..0c20721
--- /dev/null
+++ b/broadcastradio/aidl/default/VirtualProgram.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
+#include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+constexpr int kSignalQualityDigital = 100;
+constexpr int kSignalQualityNonDigital = 80;
+/**
+ * A radio program mock.
+ *
+ * This represents broadcast waves flying over the air,
+ * not an entry for a captured station in the radio tuner memory.
+ */
+struct VirtualProgram {
+    ProgramSelector selector;
+
+    std::string programName = "";
+    std::string songArtist = "";
+    std::string songTitle = "";
+
+    operator ProgramInfo() const;
+
+    /**
+     * Defines order in which virtual programs appear on the "air" with
+     * ITunerSession::scan().
+     *
+     * It's for default implementation purposes, may not be complete or correct.
+     */
+    friend bool operator<(const VirtualProgram& lhs, const VirtualProgram& rhs);
+};
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/VirtualRadio.cpp b/broadcastradio/aidl/default/VirtualRadio.cpp
new file mode 100644
index 0000000..851543b
--- /dev/null
+++ b/broadcastradio/aidl/default/VirtualRadio.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "VirtualRadio.h"
+#include <broadcastradio-utils-aidl/Utils.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+using ::aidl::android::hardware::broadcastradio::utils::makeSelectorAmfm;
+using ::aidl::android::hardware::broadcastradio::utils::makeSelectorDab;
+using ::std::string;
+using ::std::vector;
+
+VirtualRadio::VirtualRadio(const string& name, const vector<VirtualProgram>& initialList)
+    : mName(name), mPrograms(initialList) {
+    sort(mPrograms.begin(), mPrograms.end());
+}
+
+string VirtualRadio::getName() const {
+    return mName;
+}
+
+const vector<VirtualProgram>& VirtualRadio::getProgramList() const {
+    return mPrograms;
+}
+
+bool VirtualRadio::getProgram(const ProgramSelector& selector, VirtualProgram* programOut) const {
+    for (const auto& program : mPrograms) {
+        if (utils::tunesTo(selector, program.selector)) {
+            *programOut = program;
+            return true;
+        }
+    }
+    return false;
+}
+
+// get singleton of AMFM Virtual Radio
+const VirtualRadio& VirtualRadio::getAmFmRadio() {
+    // clang-format off
+    static VirtualRadio amFmRadioMock(
+        "AM/FM radio mock",
+        {
+            {makeSelectorAmfm(94900), "Wild 94.9", "Drake ft. Rihanna", "Too Good"},
+            {makeSelectorAmfm(96500), "KOIT", "Celine Dion", "All By Myself"},
+            {makeSelectorAmfm(97300), "Alice@97.3", "Drops of Jupiter", "Train"},
+            {makeSelectorAmfm(99700), "99.7 Now!", "The Chainsmokers", "Closer"},
+            {makeSelectorAmfm(101300), "101-3 KISS-FM", "Justin Timberlake", "Rock Your Body"},
+            {makeSelectorAmfm(103700), "iHeart80s @ 103.7", "Michael Jackson", "Billie Jean"},
+            {makeSelectorAmfm(106100), "106 KMEL", "Drake", "Marvins Room"},
+            {makeSelectorAmfm(700), "700 AM", "Artist700", "Title700"},
+            {makeSelectorAmfm(1700), "1700 AM", "Artist1700", "Title1700"},
+        });
+    // clang-format on
+    return amFmRadioMock;
+}
+
+// get singleton of DAB Virtual Radio
+const VirtualRadio& VirtualRadio::getDabRadio() {
+    // clang-format off
+    static VirtualRadio dabRadioMock(
+        "DAB radio mock",
+        {
+            {makeSelectorDab(0xA00001u, 0x0001u), "BBC Radio 1", "Khalid", "Talk"},
+            {makeSelectorDab(0xB00001u, 0x1001u), "Classic FM", "Jean Sibelius", "Andante Festivo"},
+            {makeSelectorDab(0xB00002u, 0x1001u), "Absolute Radio", "Coldplay", "Clocks"},
+        });
+    // clang-format on
+    return dabRadioMock;
+}
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/VirtualRadio.h b/broadcastradio/aidl/default/VirtualRadio.h
new file mode 100644
index 0000000..ae039c4
--- /dev/null
+++ b/broadcastradio/aidl/default/VirtualRadio.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "VirtualProgram.h"
+
+#include <vector>
+
+namespace aidl::android::hardware::broadcastradio {
+
+/**
+ * A radio frequency space mock.
+ *
+ * This represents all broadcast waves in the air for a given radio technology,
+ * not a captured station list in the radio tuner memory.
+ *
+ * It's meant to abstract out radio content from default tuner implementation.
+ */
+class VirtualRadio final {
+  public:
+    VirtualRadio(const std::string& name, const std::vector<VirtualProgram>& initialList);
+    std::string getName() const;
+    const std::vector<VirtualProgram>& getProgramList() const;
+    bool getProgram(const ProgramSelector& selector, VirtualProgram* program) const;
+
+    static const VirtualRadio& getAmFmRadio();
+    static const VirtualRadio& getDabRadio();
+
+  private:
+    const std::string mName;
+    std::vector<VirtualProgram> mPrograms;
+};
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/aidl/default/broadcastradio-default.rc b/broadcastradio/aidl/default/broadcastradio-default.rc
new file mode 100644
index 0000000..49389e6
--- /dev/null
+++ b/broadcastradio/aidl/default/broadcastradio-default.rc
@@ -0,0 +1,6 @@
+service vendor.broadcastradio-default /vendor/bin/hw/android.hardware.broadcastradio-service.default
+    interface aidl android.hardware.broadcastradio.IBroadcastRadio/amfm
+    interface aidl android.hardware.broadcastradio.IBroadcastRadio/dab
+    class hal
+    user audioserver
+    group audio
diff --git a/broadcastradio/aidl/default/broadcastradio-default.xml b/broadcastradio/aidl/default/broadcastradio-default.xml
new file mode 100644
index 0000000..1555822
--- /dev/null
+++ b/broadcastradio/aidl/default/broadcastradio-default.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.broadcastradio</name>
+        <fqname>IBroadcastRadio/amfm</fqname>
+        <fqname>IBroadcastRadio/dab</fqname>
+    </hal>
+</manifest>
diff --git a/broadcastradio/aidl/default/main.cpp b/broadcastradio/aidl/default/main.cpp
new file mode 100644
index 0000000..6bf20d5
--- /dev/null
+++ b/broadcastradio/aidl/default/main.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BroadcastRadio.h"
+#include "VirtualRadio.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::hardware::broadcastradio::BroadcastRadio;
+using ::aidl::android::hardware::broadcastradio::VirtualRadio;
+
+int main() {
+    android::base::SetDefaultTag("BcRadioAidlDef");
+    ABinderProcess_setThreadPoolMaxThreadCount(4);
+    ABinderProcess_startThreadPool();
+
+    const VirtualRadio& amFmRadioMock = VirtualRadio::getAmFmRadio();
+    std::shared_ptr<BroadcastRadio> broadcastRadio =
+            ::ndk::SharedRefBase::make<BroadcastRadio>(amFmRadioMock);
+    const std::string instanceAmFm = std::string() + BroadcastRadio::descriptor + "/amfm";
+    binder_status_t statusAmFm =
+            AServiceManager_addService(broadcastRadio->asBinder().get(), instanceAmFm.c_str());
+    CHECK_EQ(statusAmFm, STATUS_OK)
+            << "Failed to register Broadcast Radio AM/FM HAL implementation";
+
+    const VirtualRadio& dabRadioMock = VirtualRadio::getDabRadio();
+    std::shared_ptr<BroadcastRadio> dabRadio =
+            ::ndk::SharedRefBase::make<BroadcastRadio>(dabRadioMock);
+    const std::string instanceDab = std::string() + BroadcastRadio::descriptor + "/dab";
+    binder_status_t statusDab =
+            AServiceManager_addService(dabRadio->asBinder().get(), instanceDab.c_str());
+    CHECK_EQ(statusDab, STATUS_OK) << "Failed to register Broadcast Radio DAB HAL implementation";
+
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/broadcastradio/aidl/default/resources.h b/broadcastradio/aidl/default/resources.h
new file mode 100644
index 0000000..beffc76
--- /dev/null
+++ b/broadcastradio/aidl/default/resources.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+
+namespace aidl::android::hardware::broadcastradio::resources {
+
+constexpr int32_t kDemoPngId = 123456;
+constexpr uint8_t kDemoPng[] = {
+        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
+        0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x02, 0x00, 0x00, 0x00, 0x25,
+        0x0b, 0xe6, 0x89, 0x00, 0x00, 0x00, 0x5d, 0x49, 0x44, 0x41, 0x54, 0x68, 0xde, 0xed, 0xd9,
+        0xc1, 0x09, 0x00, 0x30, 0x08, 0x04, 0xc1, 0x33, 0xfd, 0xf7, 0x6c, 0x6a, 0xc8, 0x23, 0x04,
+        0xc9, 0x6c, 0x01, 0xc2, 0x20, 0xbe, 0x4c, 0x86, 0x57, 0x49, 0xba, 0xfb, 0xd6, 0xf4, 0xba,
+        0x3e, 0x7f, 0x4d, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x8f, 0x00, 0xbd, 0xce, 0x7f,
+        0xc0, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xb8, 0x0d, 0x32, 0xd4, 0x0c, 0x77, 0xbd,
+        0xfb, 0xc1, 0xce, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
+
+}  // namespace aidl::android::hardware::broadcastradio::resources
diff --git a/broadcastradio/aidl/vts/Android.bp b/broadcastradio/aidl/vts/Android.bp
new file mode 100644
index 0000000..b60387e
--- /dev/null
+++ b/broadcastradio/aidl/vts/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsHalBroadcastradioAidlTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    tidy_timeout_srcs: ["src/*.cpp"],
+    srcs: ["src/*.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbase",
+        "libxml2",
+    ],
+    static_libs: [
+        "android.hardware.broadcastradio-V1-ndk",
+        "android.hardware.broadcastradio@common-utils-aidl-lib",
+        "android.hardware.broadcastradio@vts-utils-lib",
+        "libgmock",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/broadcastradio/aidl/vts/OWNERS b/broadcastradio/aidl/vts/OWNERS
new file mode 100644
index 0000000..302fdd7
--- /dev/null
+++ b/broadcastradio/aidl/vts/OWNERS
@@ -0,0 +1,4 @@
+xuweilin@google.com
+oscarazu@google.com
+ericjeong@google.com
+keunyoung@google.com
diff --git a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
new file mode 100644
index 0000000..5a56846
--- /dev/null
+++ b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define EGMOCK_VERBOSE 1
+
+#include <aidl/android/hardware/broadcastradio/BnAnnouncementListener.h>
+#include <aidl/android/hardware/broadcastradio/BnTunerCallback.h>
+#include <aidl/android/hardware/broadcastradio/ConfigFlag.h>
+#include <aidl/android/hardware/broadcastradio/IBroadcastRadio.h>
+#include <aidl/android/hardware/broadcastradio/ProgramListChunk.h>
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+#include <aidl/android/hardware/broadcastradio/VendorKeyValue.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/thread_annotations.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <broadcastradio-utils-aidl/Utils.h>
+#include <broadcastradio-vts-utils/mock-timeout.h>
+#include <cutils/bitops.h>
+#include <gmock/gmock.h>
+
+#include <chrono>
+#include <optional>
+#include <regex>
+
+namespace aidl::android::hardware::broadcastradio::vts {
+
+namespace {
+
+using ::aidl::android::hardware::broadcastradio::utils::makeIdentifier;
+using ::aidl::android::hardware::broadcastradio::utils::makeSelectorAmfm;
+using ::aidl::android::hardware::broadcastradio::utils::resultToInt;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using ::std::string;
+using ::std::vector;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::ByMove;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::SaveArg;
+
+namespace bcutils = ::aidl::android::hardware::broadcastradio::utils;
+
+inline constexpr std::chrono::seconds kTuneTimeoutSec =
+        std::chrono::seconds(IBroadcastRadio::TUNER_TIMEOUT_MS * 1000);
+inline constexpr std::chrono::seconds kProgramListScanTimeoutSec =
+        std::chrono::seconds(IBroadcastRadio::LIST_COMPLETE_TIMEOUT_MS * 1000);
+
+const ConfigFlag kConfigFlagValues[] = {
+        ConfigFlag::FORCE_MONO,
+        ConfigFlag::FORCE_ANALOG,
+        ConfigFlag::FORCE_DIGITAL,
+        ConfigFlag::RDS_AF,
+        ConfigFlag::RDS_REG,
+        ConfigFlag::DAB_DAB_LINKING,
+        ConfigFlag::DAB_FM_LINKING,
+        ConfigFlag::DAB_DAB_SOFT_LINKING,
+        ConfigFlag::DAB_FM_SOFT_LINKING,
+};
+
+void printSkipped(const string& msg) {
+    const auto testInfo = testing::UnitTest::GetInstance()->current_test_info();
+    LOG(INFO) << "[  SKIPPED ] " << testInfo->test_case_name() << "." << testInfo->name()
+              << " with message: " << msg;
+}
+
+bool isValidAmFmFreq(int64_t freq) {
+    ProgramIdentifier id = bcutils::makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, freq);
+    return bcutils::isValid(id);
+}
+
+void validateRange(const AmFmBandRange& range) {
+    EXPECT_TRUE(isValidAmFmFreq(range.lowerBound));
+    EXPECT_TRUE(isValidAmFmFreq(range.upperBound));
+    EXPECT_LT(range.lowerBound, range.upperBound);
+    EXPECT_GT(range.spacing, 0u);
+    EXPECT_EQ((range.upperBound - range.lowerBound) % range.spacing, 0u);
+}
+
+bool supportsFM(const AmFmRegionConfig& config) {
+    for (const auto& range : config.ranges) {
+        if (bcutils::getBand(range.lowerBound) == bcutils::FrequencyBand::FM) {
+            return true;
+        }
+    }
+    return false;
+}
+
+}  // namespace
+
+class TunerCallbackMock : public BnTunerCallback {
+  public:
+    TunerCallbackMock();
+
+    MOCK_METHOD2(onTuneFailed, ScopedAStatus(Result, const ProgramSelector&));
+    MOCK_TIMEOUT_METHOD1(onCurrentProgramInfoChangedMock, ScopedAStatus(const ProgramInfo&));
+    ScopedAStatus onCurrentProgramInfoChanged(const ProgramInfo& info) override;
+    ScopedAStatus onProgramListUpdated(const ProgramListChunk& chunk) override;
+    MOCK_METHOD1(onAntennaStateChange, ScopedAStatus(bool connected));
+    MOCK_METHOD1(onParametersUpdated, ScopedAStatus(const vector<VendorKeyValue>& parameters));
+    MOCK_METHOD2(onConfigFlagUpdated, ScopedAStatus(ConfigFlag in_flag, bool in_value));
+    MOCK_TIMEOUT_METHOD0(onProgramListReady, void());
+
+    std::mutex mLock;
+    bcutils::ProgramInfoSet mProgramList GUARDED_BY(mLock);
+};
+
+struct AnnouncementListenerMock : public BnAnnouncementListener {
+    MOCK_METHOD1(onListUpdated, ScopedAStatus(const vector<Announcement>&));
+};
+
+class BroadcastRadioHalTest : public testing::TestWithParam<string> {
+  protected:
+    void SetUp() override;
+    void TearDown() override;
+
+    bool getAmFmRegionConfig(bool full, AmFmRegionConfig* config);
+    std::optional<bcutils::ProgramInfoSet> getProgramList();
+    std::optional<bcutils::ProgramInfoSet> getProgramList(const ProgramFilter& filter);
+
+    std::shared_ptr<IBroadcastRadio> mModule;
+    Properties mProperties;
+    std::shared_ptr<TunerCallbackMock> mCallback = SharedRefBase::make<TunerCallbackMock>();
+};
+
+MATCHER_P(InfoHasId, id, string(negation ? "does not contain" : "contains") + " " + id.toString()) {
+    vector<int> ids = bcutils::getAllIds(arg.selector, id.type);
+    return ids.end() != find(ids.begin(), ids.end(), id.value);
+}
+
+TunerCallbackMock::TunerCallbackMock() {
+    EXPECT_TIMEOUT_CALL(*this, onCurrentProgramInfoChangedMock, _).Times(AnyNumber());
+
+    // we expect the antenna is connected through the whole test
+    EXPECT_CALL(*this, onAntennaStateChange(false)).Times(0);
+}
+
+ScopedAStatus TunerCallbackMock::onCurrentProgramInfoChanged(const ProgramInfo& info) {
+    for (const auto& id : info.selector) {
+        EXPECT_NE(id.type, IdentifierType::INVALID);
+    }
+
+    IdentifierType logically = info.logicallyTunedTo.type;
+    // This field is required for currently tuned program and should be INVALID
+    // for entries from the program list.
+    EXPECT_TRUE(logically == IdentifierType::AMFM_FREQUENCY_KHZ ||
+                logically == IdentifierType::RDS_PI ||
+                logically == IdentifierType::HD_STATION_ID_EXT ||
+                logically == IdentifierType::DAB_SID_EXT ||
+                logically == IdentifierType::DRMO_SERVICE_ID ||
+                logically == IdentifierType::SXM_SERVICE_ID ||
+                (logically >= IdentifierType::VENDOR_START &&
+                 logically <= IdentifierType::VENDOR_END) ||
+                logically > IdentifierType::SXM_CHANNEL);
+
+    IdentifierType physically = info.physicallyTunedTo.type;
+    // ditto (see "logically" above)
+    EXPECT_TRUE(physically == IdentifierType::AMFM_FREQUENCY_KHZ ||
+                physically == IdentifierType::DAB_ENSEMBLE ||
+                physically == IdentifierType::DRMO_FREQUENCY_KHZ ||
+                physically == IdentifierType::SXM_CHANNEL ||
+                (physically >= IdentifierType::VENDOR_START &&
+                 physically <= IdentifierType::VENDOR_END) ||
+                physically > IdentifierType::SXM_CHANNEL);
+
+    if (logically == IdentifierType::AMFM_FREQUENCY_KHZ) {
+        std::optional<string> ps = bcutils::getMetadataString(info, Metadata::rdsPs);
+        if (ps.has_value()) {
+            EXPECT_NE(::android::base::Trim(*ps), "")
+                    << "Don't use empty RDS_PS as an indicator of missing RSD PS data.";
+        }
+    }
+
+    return onCurrentProgramInfoChangedMock(info);
+}
+
+ScopedAStatus TunerCallbackMock::onProgramListUpdated(const ProgramListChunk& chunk) {
+    std::lock_guard<std::mutex> lk(mLock);
+
+    updateProgramList(chunk, &mProgramList);
+
+    if (chunk.complete) {
+        onProgramListReady();
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+void BroadcastRadioHalTest::SetUp() {
+    EXPECT_EQ(mModule.get(), nullptr) << "Module is already open";
+
+    // lookup AIDL service (radio module)
+    AIBinder* binder = AServiceManager_waitForService(GetParam().c_str());
+    ASSERT_NE(binder, nullptr);
+    mModule = IBroadcastRadio::fromBinder(ndk::SpAIBinder(binder));
+    ASSERT_NE(mModule, nullptr) << "Couldn't find broadcast radio HAL implementation";
+
+    // get module properties
+    auto propResult = mModule->getProperties(&mProperties);
+
+    ASSERT_TRUE(propResult.isOk());
+    EXPECT_FALSE(mProperties.maker.empty());
+    EXPECT_FALSE(mProperties.product.empty());
+    EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u);
+
+    // set callback
+    EXPECT_TRUE(mModule->setTunerCallback(mCallback).isOk());
+}
+
+void BroadcastRadioHalTest::TearDown() {
+    if (mModule) {
+        ASSERT_TRUE(mModule->unsetTunerCallback().isOk());
+    }
+}
+
+bool BroadcastRadioHalTest::getAmFmRegionConfig(bool full, AmFmRegionConfig* config) {
+    auto halResult = mModule->getAmFmRegionConfig(full, config);
+
+    if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        return false;
+    }
+
+    EXPECT_TRUE(halResult.isOk());
+    return halResult.isOk();
+}
+
+std::optional<bcutils::ProgramInfoSet> BroadcastRadioHalTest::getProgramList() {
+    ProgramFilter emptyFilter = {};
+    return getProgramList(emptyFilter);
+}
+
+std::optional<bcutils::ProgramInfoSet> BroadcastRadioHalTest::getProgramList(
+        const ProgramFilter& filter) {
+    EXPECT_TIMEOUT_CALL(*mCallback, onProgramListReady).Times(AnyNumber());
+
+    auto startResult = mModule->startProgramListUpdates(filter);
+
+    if (startResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        printSkipped("Program list not supported");
+        return std::nullopt;
+    }
+    EXPECT_TRUE(startResult.isOk());
+    if (!startResult.isOk()) {
+        return std::nullopt;
+    }
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onProgramListReady, kProgramListScanTimeoutSec);
+
+    auto stopResult = mModule->stopProgramListUpdates();
+
+    EXPECT_TRUE(stopResult.isOk());
+
+    return mCallback->mProgramList;
+}
+
+/**
+ * Test setting tuner callback to null.
+ *
+ * Verifies that:
+ *  - Setting to a null tuner callback results with INVALID_ARGUMENTS.
+ */
+TEST_P(BroadcastRadioHalTest, TunerCallbackFailsWithNull) {
+    LOG(DEBUG) << "TunerCallbackFailsWithNull Test";
+
+    auto halResult = mModule->setTunerCallback(nullptr);
+
+    EXPECT_EQ(halResult.getServiceSpecificError(), resultToInt(Result::INVALID_ARGUMENTS));
+}
+
+/**
+ * Test fetching AM/FM regional configuration.
+ *
+ * Verifies that:
+ *  - AM/FM regional configuration is either set at startup or not supported at all by the hardware;
+ *  - FM Deemphasis and RDS are correctly configured for FM-capable radio;
+ */
+TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfig) {
+    LOG(DEBUG) << "GetAmFmRegionConfig Test";
+
+    AmFmRegionConfig config;
+
+    bool supported = getAmFmRegionConfig(/* full= */ false, &config);
+
+    if (!supported) {
+        printSkipped("AM/FM not supported");
+        return;
+    }
+
+    EXPECT_LE(popcountll(static_cast<unsigned long long>(config.fmDeemphasis)), 1);
+    EXPECT_LE(popcountll(static_cast<unsigned long long>(config.fmRds)), 1);
+
+    if (supportsFM(config)) {
+        EXPECT_EQ(popcountll(static_cast<unsigned long long>(config.fmDeemphasis)), 1);
+    }
+}
+
+/**
+ * Test fetching ranges of AM/FM regional configuration.
+ *
+ * Verifies that:
+ *  - AM/FM regional configuration is either set at startup or not supported at all by the hardware;
+ *  - there is at least one AM/FM band configured;
+ *  - all channel grids (frequency ranges and spacings) are valid;
+ *  - seek spacing is a multiple of the manual spacing value.
+ */
+TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfigRanges) {
+    LOG(DEBUG) << "GetAmFmRegionConfigRanges Test";
+
+    AmFmRegionConfig config;
+
+    bool supported = getAmFmRegionConfig(/* full= */ false, &config);
+
+    if (!supported) {
+        printSkipped("AM/FM not supported");
+        return;
+    }
+
+    EXPECT_GT(config.ranges.size(), 0u);
+    for (const auto& range : config.ranges) {
+        validateRange(range);
+        EXPECT_EQ(range.seekSpacing % range.spacing, 0u);
+        EXPECT_GE(range.seekSpacing, range.spacing);
+    }
+}
+
+/**
+ * Test fetching FM regional capabilities.
+ *
+ * Verifies that:
+ *  - AM/FM regional capabilities are either available or not supported at all by the hardware;
+ *  - there is at least one de-emphasis filter mode supported for FM-capable radio;
+ */
+TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfigCapabilitiesForFM) {
+    LOG(DEBUG) << "GetAmFmRegionConfigCapabilitiesForFM Test";
+
+    AmFmRegionConfig config;
+
+    bool supported = getAmFmRegionConfig(/* full= */ true, &config);
+
+    if (supported && supportsFM(config)) {
+        EXPECT_GE(popcountll(static_cast<unsigned long long>(config.fmDeemphasis)), 1);
+    } else {
+        printSkipped("FM not supported");
+    }
+}
+
+/**
+ * Test fetching the ranges of AM/FM regional capabilities.
+ *
+ * Verifies that:
+ *  - AM/FM regional capabilities are either available or not supported at all by the hardware;
+ *  - there is at least one AM/FM range supported;
+ *  - all channel grids (frequency ranges and spacings) are valid;
+ *  - seek spacing is not set.
+ */
+TEST_P(BroadcastRadioHalTest, GetAmFmRegionConfigCapabilitiesRanges) {
+    LOG(DEBUG) << "GetAmFmRegionConfigCapabilitiesRanges Test";
+
+    AmFmRegionConfig config;
+
+    bool supported = getAmFmRegionConfig(/* full= */ true, &config);
+
+    if (!supported) {
+        printSkipped("AM/FM not supported");
+        return;
+    }
+
+    EXPECT_GT(config.ranges.size(), 0u);
+
+    for (const auto& range : config.ranges) {
+        validateRange(range);
+        EXPECT_EQ(range.seekSpacing, 0u);
+    }
+}
+
+/**
+ * Test fetching DAB regional configuration.
+ *
+ * Verifies that:
+ *  - DAB regional configuration is either set at startup or not supported at all by the hardware;
+ *  - all channel labels match correct format;
+ *  - all channel frequencies are in correct range.
+ */
+TEST_P(BroadcastRadioHalTest, GetDabRegionConfig) {
+    LOG(DEBUG) << "GetDabRegionConfig Test";
+    vector<DabTableEntry> config;
+
+    auto halResult = mModule->getDabRegionConfig(&config);
+
+    if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        printSkipped("DAB not supported");
+        return;
+    }
+    ASSERT_TRUE(halResult.isOk());
+
+    std::regex re("^[A-Z0-9][A-Z0-9 ]{0,5}[A-Z0-9]$");
+
+    for (const auto& entry : config) {
+        EXPECT_TRUE(std::regex_match(string(entry.label), re));
+
+        ProgramIdentifier id =
+                bcutils::makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, entry.frequencyKhz);
+        EXPECT_TRUE(bcutils::isValid(id));
+    }
+}
+
+/**
+ * Test tuning without tuner callback set.
+ *
+ * Verifies that:
+ *  - No tuner callback set results in INVALID_STATE, regardless of whether the selector is
+ * supported.
+ */
+TEST_P(BroadcastRadioHalTest, TuneFailsWithoutTunerCallback) {
+    LOG(DEBUG) << "TuneFailsWithoutTunerCallback Test";
+
+    mModule->unsetTunerCallback();
+    int64_t freq = 90900;  // 90.9 FM
+    ProgramSelector sel = makeSelectorAmfm(freq);
+
+    auto result = mModule->tune(sel);
+
+    EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE));
+}
+
+/**
+ * Test tuning with selectors that can be not supported.
+ *
+ * Verifies that:
+ *  - if the selector is not supported, an invalid value results with NOT_SUPPORTED, regardless of
+ *    whether it is valid;
+ *  - if it is supported, the test is ignored;
+ */
+TEST_P(BroadcastRadioHalTest, TuneFailsWithNotSupported) {
+    LOG(DEBUG) << "TuneFailsWithInvalid Test";
+
+    vector<ProgramIdentifier> supportTestId = {
+            makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 0),           // invalid
+            makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 94900),       // valid
+            makeIdentifier(IdentifierType::RDS_PI, 0x10000),                 // invalid
+            makeIdentifier(IdentifierType::RDS_PI, 0x1001),                  // valid
+            makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000),  // invalid
+            makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x10000001),   // valid
+            makeIdentifier(IdentifierType::DAB_SID_EXT, 0),                  // invalid
+            makeIdentifier(IdentifierType::DAB_SID_EXT, 0xA00001),           // valid
+            makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000),    // invalid
+            makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x10000001),     // valid
+            makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x100000000),     // invalid
+            makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x10000001),      // valid
+    };
+
+    auto notSupportedError = resultToInt(Result::NOT_SUPPORTED);
+    for (const auto& id : supportTestId) {
+        ProgramSelector sel{id, {}};
+
+        auto result = mModule->tune(sel);
+
+        if (!bcutils::isSupported(mProperties, sel)) {
+            EXPECT_EQ(result.getServiceSpecificError(), notSupportedError);
+        }
+    }
+}
+
+/**
+ * Test tuning with invalid selectors.
+ *
+ * Verifies that:
+ *  - if the selector is not supported, it's ignored;
+ *  - if it is supported, an invalid value results with INVALID_ARGUMENTS;
+ */
+TEST_P(BroadcastRadioHalTest, TuneFailsWithInvalid) {
+    LOG(DEBUG) << "TuneFailsWithInvalid Test";
+
+    vector<ProgramIdentifier> invalidId = {
+            makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, 0),
+            makeIdentifier(IdentifierType::RDS_PI, 0x10000),
+            makeIdentifier(IdentifierType::HD_STATION_ID_EXT, 0x100000000),
+            makeIdentifier(IdentifierType::DAB_SID_EXT, 0),
+            makeIdentifier(IdentifierType::DRMO_SERVICE_ID, 0x100000000),
+            makeIdentifier(IdentifierType::SXM_SERVICE_ID, 0x100000000),
+    };
+
+    auto invalidArgumentsError = resultToInt(Result::INVALID_ARGUMENTS);
+    for (const auto& id : invalidId) {
+        ProgramSelector sel{id, {}};
+
+        auto result = mModule->tune(sel);
+
+        if (bcutils::isSupported(mProperties, sel)) {
+            EXPECT_EQ(result.getServiceSpecificError(), invalidArgumentsError);
+        }
+    }
+}
+
+/**
+ * Test tuning with empty program selector.
+ *
+ * Verifies that:
+ *  - tune fails with NOT_SUPPORTED when program selector is not initialized.
+ */
+TEST_P(BroadcastRadioHalTest, TuneFailsWithEmpty) {
+    LOG(DEBUG) << "TuneFailsWithEmpty Test";
+
+    // Program type is 1-based, so 0 will always be invalid.
+    ProgramSelector sel = {};
+
+    auto result = mModule->tune(sel);
+
+    ASSERT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED));
+}
+
+/**
+ * Test tuning with FM selector.
+ *
+ * Verifies that:
+ *  - if AM/FM selector is not supported, the method returns NOT_SUPPORTED;
+ *  - if it is supported, the method succeeds;
+ *  - after a successful tune call, onCurrentProgramInfoChanged callback is
+ *    invoked carrying a proper selector;
+ *  - program changes exactly to what was requested.
+ */
+TEST_P(BroadcastRadioHalTest, FmTune) {
+    LOG(DEBUG) << "FmTune Test";
+
+    int64_t freq = 90900;  // 90.9 FM
+    ProgramSelector sel = makeSelectorAmfm(freq);
+    // try tuning
+    ProgramInfo infoCb = {};
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock,
+                        InfoHasId(makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, freq)))
+            .Times(AnyNumber())
+            .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(ndk::ScopedAStatus::ok()))))
+            .WillRepeatedly(testing::InvokeWithoutArgs([] { return ndk::ScopedAStatus::ok(); }));
+
+    auto result = mModule->tune(sel);
+
+    // expect a failure if it's not supported
+    if (!bcutils::isSupported(mProperties, sel)) {
+        EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED));
+        return;
+    }
+
+    // expect a callback if it succeeds
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+
+    LOG(DEBUG) << "Current program info: " << infoCb.toString();
+
+    // it should tune exactly to what was requested
+    vector<int> freqs = bcutils::getAllIds(infoCb.selector, IdentifierType::AMFM_FREQUENCY_KHZ);
+    EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq))
+            << "FM freq " << freq << " kHz is not sent back by callback.";
+}
+
+/**
+ * Test tuning with DAB selector.
+ *
+ * Verifies that:
+ *  - if DAB selector is not supported, the method returns NOT_SUPPORTED;
+ *  - if it is supported, the method succeeds;
+ *  - after a successful tune call, onCurrentProgramInfoChanged callback is
+ *    invoked carrying a proper selector;
+ *  - program changes exactly to what was requested.
+ */
+TEST_P(BroadcastRadioHalTest, DabTune) {
+    LOG(DEBUG) << "DabTune Test";
+    vector<DabTableEntry> config;
+
+    auto halResult = mModule->getDabRegionConfig(&config);
+
+    if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        printSkipped("DAB not supported");
+        return;
+    }
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_NE(config.size(), 0U);
+
+    // TODO(245787803): use a DAB frequency that can actually be tuned to.
+    ProgramSelector sel = {};
+    int64_t freq = config[config.size() / 2].frequencyKhz;
+    sel.primaryId = makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, freq);
+
+    // try tuning
+    ProgramInfo infoCb = {};
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock,
+                        InfoHasId(makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, freq)))
+            .Times(AnyNumber())
+            .WillOnce(
+                    DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(ndk::ScopedAStatus::ok()))));
+
+    auto result = mModule->tune(sel);
+
+    // expect a failure if it's not supported
+    if (!bcutils::isSupported(mProperties, sel)) {
+        EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::NOT_SUPPORTED));
+        return;
+    }
+
+    // expect a callback if it succeeds
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+    LOG(DEBUG) << "Current program info: " << infoCb.toString();
+
+    // it should tune exactly to what was requested
+    vector<int> freqs = bcutils::getAllIds(infoCb.selector, IdentifierType::DAB_FREQUENCY_KHZ);
+    EXPECT_NE(freqs.end(), find(freqs.begin(), freqs.end(), freq))
+            << "DAB freq " << freq << " kHz is not sent back by callback.";
+    ;
+}
+
+/**
+ * Test seeking to next/prev station via IBroadcastRadio::seek().
+ *
+ * Verifies that:
+ *  - the method succeeds;
+ *  - the program info is changed within kTuneTimeoutSec;
+ *  - works both directions and with or without skipping sub-channel.
+ */
+TEST_P(BroadcastRadioHalTest, Seek) {
+    LOG(DEBUG) << "Seek Test";
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber());
+
+    auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true);
+
+    if (result.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        printSkipped("Seek not supported");
+        return;
+    }
+
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber());
+
+    result = mModule->seek(/* in_directionUp= */ false, /* in_skipSubChannel= */ false);
+
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+}
+
+/**
+ * Test seeking without tuner callback set.
+ *
+ * Verifies that:
+ *  - No tuner callback set results in INVALID_STATE.
+ */
+TEST_P(BroadcastRadioHalTest, SeekFailsWithoutTunerCallback) {
+    LOG(DEBUG) << "SeekFailsWithoutTunerCallback Test";
+
+    mModule->unsetTunerCallback();
+
+    auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true);
+
+    EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE));
+
+    result = mModule->seek(/* in_directionUp= */ false, /* in_skipSubChannel= */ false);
+
+    EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE));
+}
+
+/**
+ * Test step operation.
+ *
+ * Verifies that:
+ *  - the method succeeds or returns NOT_SUPPORTED;
+ *  - the program info is changed within kTuneTimeoutSec if the method succeeded;
+ *  - works both directions.
+ */
+TEST_P(BroadcastRadioHalTest, Step) {
+    LOG(DEBUG) << "Step Test";
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber());
+
+    auto result = mModule->step(/* in_directionUp= */ true);
+
+    if (result.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        printSkipped("Step not supported");
+        return;
+    }
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+
+    EXPECT_TIMEOUT_CALL(*mCallback, onCurrentProgramInfoChangedMock, _).Times(AnyNumber());
+
+    result = mModule->step(/* in_directionUp= */ false);
+
+    EXPECT_TRUE(result.isOk());
+    EXPECT_TIMEOUT_CALL_WAIT(*mCallback, onCurrentProgramInfoChangedMock, kTuneTimeoutSec);
+}
+
+/**
+ * Test step operation without tuner callback set.
+ *
+ * Verifies that:
+ *  - No tuner callback set results in INVALID_STATE.
+ */
+TEST_P(BroadcastRadioHalTest, StepFailsWithoutTunerCallback) {
+    LOG(DEBUG) << "StepFailsWithoutTunerCallback Test";
+
+    mModule->unsetTunerCallback();
+
+    auto result = mModule->step(/* in_directionUp= */ true);
+
+    EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE));
+
+    result = mModule->step(/* in_directionUp= */ false);
+
+    EXPECT_EQ(result.getServiceSpecificError(), resultToInt(Result::INVALID_STATE));
+}
+
+/**
+ * Test tune cancellation.
+ *
+ * Verifies that:
+ *  - the method does not crash after being invoked multiple times.
+ *
+ * Since cancel() might be called after the HAL completes an operation (tune, seek, and step)
+ * and before the callback completions, the operation might not be actually canceled and the
+ * effect of cancel() is not deterministic to be tested here.
+ */
+TEST_P(BroadcastRadioHalTest, Cancel) {
+    LOG(DEBUG) << "Cancel Test";
+
+    auto notSupportedError = resultToInt(Result::NOT_SUPPORTED);
+    for (int i = 0; i < 10; i++) {
+        auto result = mModule->seek(/* in_directionUp= */ true, /* in_skipSubChannel= */ true);
+
+        if (result.getServiceSpecificError() == notSupportedError) {
+            printSkipped("Cancel is skipped because of seek not supported");
+            return;
+        }
+        EXPECT_TRUE(result.isOk());
+
+        auto cancelResult = mModule->cancel();
+
+        ASSERT_TRUE(cancelResult.isOk());
+    }
+}
+
+/**
+ * Test IBroadcastRadio::get|setParameters() methods called with no parameters.
+ *
+ * Verifies that:
+ *  - callback is called for empty parameters set.
+ */
+TEST_P(BroadcastRadioHalTest, NoParameters) {
+    LOG(DEBUG) << "NoParameters Test";
+
+    vector<VendorKeyValue> parametersResults = {};
+
+    auto halResult = mModule->setParameters({}, &parametersResults);
+
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_EQ(parametersResults.size(), 0u);
+
+    parametersResults.clear();
+
+    halResult = mModule->getParameters({}, &parametersResults);
+
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_EQ(parametersResults.size(), 0u);
+}
+
+/**
+ * Test IBroadcastRadio::get|setParameters() methods called with unknown parameters.
+ *
+ * Verifies that:
+ *  - unknown parameters are ignored;
+ *  - callback is called also for empty results set.
+ */
+TEST_P(BroadcastRadioHalTest, UnknownParameters) {
+    LOG(DEBUG) << "UnknownParameters Test";
+
+    vector<VendorKeyValue> parametersResults = {};
+
+    auto halResult =
+            mModule->setParameters({{"com.android.unknown", "sample"}}, &parametersResults);
+
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_EQ(parametersResults.size(), 0u);
+
+    parametersResults.clear();
+
+    halResult = mModule->getParameters({"com.android.unknown*", "sample"}, &parametersResults);
+
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_EQ(parametersResults.size(), 0u);
+}
+
+/**
+ * Test geting image of invalid ID.
+ *
+ * Verifies that:
+ * - getImage call handles argument 0 gracefully.
+ */
+TEST_P(BroadcastRadioHalTest, GetNoImage) {
+    LOG(DEBUG) << "GetNoImage Test";
+    vector<uint8_t> rawImage;
+
+    auto result = mModule->getImage(IBroadcastRadio::INVALID_IMAGE, &rawImage);
+
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(rawImage.size(), 0u);
+}
+
+/**
+ * Test getting config flags.
+ *
+ * Verifies that:
+ * - isConfigFlagSet either succeeds or ends with NOT_SUPPORTED or INVALID_STATE;
+ * - call success or failure is consistent with setConfigFlag.
+ */
+TEST_P(BroadcastRadioHalTest, FetchConfigFlags) {
+    LOG(DEBUG) << "FetchConfigFlags Test";
+
+    for (const auto& flag : kConfigFlagValues) {
+        bool gotValue = false;
+
+        auto halResult = mModule->isConfigFlagSet(flag, &gotValue);
+
+        if (halResult.getServiceSpecificError() != resultToInt(Result::NOT_SUPPORTED) &&
+            halResult.getServiceSpecificError() != resultToInt(Result::INVALID_STATE)) {
+            ASSERT_TRUE(halResult.isOk());
+        }
+
+        // set must fail or succeed the same way as get
+        auto setResult = mModule->setConfigFlag(flag, /* value= */ false);
+
+        EXPECT_TRUE((halResult.isOk() && setResult.isOk()) ||
+                    (halResult.getServiceSpecificError()) == setResult.getServiceSpecificError());
+
+        setResult = mModule->setConfigFlag(flag, /* value= */ true);
+
+        EXPECT_TRUE((halResult.isOk() && setResult.isOk()) ||
+                    (halResult.getServiceSpecificError()) == setResult.getServiceSpecificError());
+    }
+}
+
+/**
+ * Test setting config flags.
+ *
+ * Verifies that:
+ * - setConfigFlag either succeeds or ends with NOT_SUPPORTED or INVALID_STATE;
+ * - isConfigFlagSet reflects the state requested immediately after the set call.
+ */
+TEST_P(BroadcastRadioHalTest, SetConfigFlags) {
+    LOG(DEBUG) << "SetConfigFlags Test";
+
+    auto get = [&](ConfigFlag flag) -> bool {
+        bool* gotValue = nullptr;
+
+        auto halResult = mModule->isConfigFlagSet(flag, gotValue);
+
+        EXPECT_FALSE(gotValue == nullptr);
+        EXPECT_TRUE(halResult.isOk());
+        return *gotValue;
+    };
+
+    auto notSupportedError = resultToInt(Result::NOT_SUPPORTED);
+    auto invalidStateError = resultToInt(Result::INVALID_STATE);
+    for (const auto& flag : kConfigFlagValues) {
+        auto result = mModule->setConfigFlag(flag, /* value= */ false);
+
+        if (result.getServiceSpecificError() == notSupportedError ||
+            result.getServiceSpecificError() == invalidStateError) {
+            // setting to true must result in the same error as false
+            auto secondResult = mModule->setConfigFlag(flag, /* value= */ true);
+
+            EXPECT_TRUE((result.isOk() && secondResult.isOk()) ||
+                        result.getServiceSpecificError() == secondResult.getServiceSpecificError());
+            continue;
+        } else {
+            ASSERT_TRUE(result.isOk());
+        }
+
+        // verify false is set
+        bool value = get(flag);
+        EXPECT_FALSE(value);
+
+        // try setting true this time
+        result = mModule->setConfigFlag(flag, /* value= */ true);
+
+        ASSERT_TRUE(result.isOk());
+        value = get(flag);
+        EXPECT_TRUE(value);
+
+        // false again
+        result = mModule->setConfigFlag(flag, /* value= */ false);
+
+        ASSERT_TRUE(result.isOk());
+        value = get(flag);
+        EXPECT_FALSE(value);
+    }
+}
+
+/**
+ * Test getting program list using empty program filter.
+ *
+ * Verifies that:
+ * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED;
+ * - the complete list is fetched within kProgramListScanTimeoutSec;
+ * - stopProgramListUpdates does not crash.
+ */
+TEST_P(BroadcastRadioHalTest, GetProgramListFromEmptyFilter) {
+    LOG(DEBUG) << "GetProgramListFromEmptyFilter Test";
+
+    getProgramList();
+}
+
+/**
+ * Test getting program list using AMFM frequency program filter.
+ *
+ * Verifies that:
+ * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED;
+ * - the complete list is fetched within kProgramListScanTimeoutSec;
+ * - stopProgramListUpdates does not crash;
+ * - result for startProgramListUpdates using a filter with AMFM_FREQUENCY_KHZ value of the first
+ *   AMFM program matches the expected result.
+ */
+TEST_P(BroadcastRadioHalTest, GetProgramListFromAmFmFilter) {
+    LOG(DEBUG) << "GetProgramListFromAmFmFilter Test";
+
+    std::optional<bcutils::ProgramInfoSet> completeList = getProgramList();
+    if (!completeList) {
+        printSkipped("No program list available");
+        return;
+    }
+
+    ProgramFilter amfmFilter = {};
+    int expectedResultSize = 0;
+    uint64_t expectedFreq = 0;
+    for (const auto& program : *completeList) {
+        vector<int> amfmIds =
+                bcutils::getAllIds(program.selector, IdentifierType::AMFM_FREQUENCY_KHZ);
+        EXPECT_LE(amfmIds.size(), 1u);
+        if (amfmIds.size() == 0) {
+            continue;
+        }
+
+        if (expectedResultSize == 0) {
+            expectedFreq = amfmIds[0];
+            amfmFilter.identifiers = {
+                    makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, expectedFreq)};
+            expectedResultSize = 1;
+        } else if (amfmIds[0] == expectedFreq) {
+            expectedResultSize++;
+        }
+    }
+
+    if (expectedResultSize == 0) {
+        printSkipped("No Am/FM programs available");
+        return;
+    }
+    std::optional<bcutils::ProgramInfoSet> amfmList = getProgramList(amfmFilter);
+    ASSERT_EQ(amfmList->size(), expectedResultSize) << "amfm filter result size is wrong";
+}
+
+/**
+ * Test getting program list using DAB ensemble program filter.
+ *
+ * Verifies that:
+ * - startProgramListUpdates either succeeds or returns NOT_SUPPORTED;
+ * - the complete list is fetched within kProgramListScanTimeoutSec;
+ * - stopProgramListUpdates does not crash;
+ * - result for startProgramListUpdates using a filter with DAB_ENSEMBLE value of the first DAB
+ *   program matches the expected result.
+ */
+TEST_P(BroadcastRadioHalTest, GetProgramListFromDabFilter) {
+    LOG(DEBUG) << "GetProgramListFromDabFilter Test";
+
+    std::optional<bcutils::ProgramInfoSet> completeList = getProgramList();
+    if (!completeList) {
+        printSkipped("No program list available");
+        return;
+    }
+
+    ProgramFilter dabFilter = {};
+    int expectedResultSize = 0;
+    uint64_t expectedEnsemble = 0;
+    for (const auto& program : *completeList) {
+        auto dabEnsembles = bcutils::getAllIds(program.selector, IdentifierType::DAB_ENSEMBLE);
+        EXPECT_LE(dabEnsembles.size(), 1u);
+        if (dabEnsembles.size() == 0) {
+            continue;
+        }
+
+        if (expectedResultSize == 0) {
+            expectedEnsemble = dabEnsembles[0];
+            dabFilter.identifiers = {
+                    makeIdentifier(IdentifierType::DAB_ENSEMBLE, expectedEnsemble)};
+            expectedResultSize = 1;
+        } else if (dabEnsembles[0] == expectedEnsemble) {
+            expectedResultSize++;
+        }
+    }
+
+    if (expectedResultSize == 0) {
+        printSkipped("No DAB programs available");
+        return;
+    }
+    std::optional<bcutils::ProgramInfoSet> dabList = getProgramList(dabFilter);
+    ASSERT_EQ(dabList->size(), expectedResultSize) << "dab filter result size is wrong";
+}
+
+/**
+ * Test HD_STATION_NAME correctness.
+ *
+ * Verifies that if a program on the list contains HD_STATION_NAME identifier:
+ *  - the program provides station name in its metadata;
+ *  - the identifier matches the name;
+ *  - there is only one identifier of that type.
+ */
+TEST_P(BroadcastRadioHalTest, HdRadioStationNameId) {
+    LOG(DEBUG) << "HdRadioStationNameId Test";
+
+    std::optional<bcutils::ProgramInfoSet> list = getProgramList();
+    if (!list) {
+        printSkipped("No program list");
+        return;
+    }
+
+    for (const auto& program : *list) {
+        vector<int> nameIds = bcutils::getAllIds(program.selector, IdentifierType::HD_STATION_NAME);
+        EXPECT_LE(nameIds.size(), 1u);
+        if (nameIds.size() == 0) {
+            continue;
+        }
+
+        std::optional<string> name = bcutils::getMetadataString(program, Metadata::programName);
+        if (!name) {
+            name = bcutils::getMetadataString(program, Metadata::rdsPs);
+        }
+        ASSERT_TRUE(name.has_value());
+
+        ProgramIdentifier expectedId = bcutils::makeHdRadioStationName(*name);
+        EXPECT_EQ(nameIds[0], expectedId.value);
+    }
+}
+
+/**
+ * Test announcement listener registration.
+ *
+ * Verifies that:
+ *  - registerAnnouncementListener either succeeds or returns NOT_SUPPORTED;
+ *  - if it succeeds, it returns a valid close handle (which is a nullptr otherwise);
+ *  - closing handle does not crash.
+ */
+TEST_P(BroadcastRadioHalTest, AnnouncementListenerRegistration) {
+    LOG(DEBUG) << "AnnouncementListenerRegistration Test";
+    std::shared_ptr<AnnouncementListenerMock> listener =
+            SharedRefBase::make<AnnouncementListenerMock>();
+    std::shared_ptr<ICloseHandle> closeHandle = nullptr;
+
+    auto halResult = mModule->registerAnnouncementListener(listener, {AnnouncementType::EMERGENCY},
+                                                           &closeHandle);
+
+    if (halResult.getServiceSpecificError() == resultToInt(Result::NOT_SUPPORTED)) {
+        ASSERT_EQ(closeHandle.get(), nullptr);
+        printSkipped("Announcements not supported");
+        return;
+    }
+
+    ASSERT_TRUE(halResult.isOk());
+    ASSERT_NE(closeHandle.get(), nullptr);
+
+    closeHandle->close();
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BroadcastRadioHalTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, BroadcastRadioHalTest,
+        testing::ValuesIn(::android::getAidlHalInstanceNames(IBroadcastRadio::descriptor)),
+        ::android::PrintInstanceNameToString);
+
+}  // namespace aidl::android::hardware::broadcastradio::vts
+
+int main(int argc, char** argv) {
+    android::base::SetDefaultTag("BcRadio.vts");
+    android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(4);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/broadcastradio/common/utilsaidl/Android.bp b/broadcastradio/common/utilsaidl/Android.bp
new file mode 100644
index 0000000..fa6de19
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/Android.bp
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_library_static {
+    name: "android.hardware.broadcastradio@common-utils-aidl-lib",
+    vendor_available: true,
+    relative_install_path: "hw",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-error=implicit-fallthrough",
+    ],
+    cppflags: [
+        "-std=c++1z",
+    ],
+    srcs: [
+        "Utils.cpp",
+    ],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "android.hardware.broadcastradio-V1-ndk",
+        "libbase",
+    ],
+    static_libs: [
+        "libmath",
+    ],
+}
diff --git a/broadcastradio/common/utilsaidl/Utils.cpp b/broadcastradio/common/utilsaidl/Utils.cpp
new file mode 100644
index 0000000..52c7b40
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/Utils.cpp
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BcRadioAidlDef.utils"
+
+#include "broadcastradio-utils-aidl/Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
+#include <math/HashCombine.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+namespace {
+
+using ::android::base::EqualsIgnoreCase;
+using ::std::string;
+using ::std::vector;
+
+const int64_t kValueForNotFoundIdentifier = 0;
+
+bool bothHaveId(const ProgramSelector& a, const ProgramSelector& b, const IdentifierType& type) {
+    return hasId(a, type) && hasId(b, type);
+}
+
+bool haveEqualIds(const ProgramSelector& a, const ProgramSelector& b, const IdentifierType& type) {
+    if (!bothHaveId(a, b, type)) {
+        return false;
+    }
+    /* We should check all Ids of a given type (ie. other AF),
+     * but it doesn't matter for default implementation.
+     */
+    return getId(a, type) == getId(b, type);
+}
+
+int getHdSubchannel(const ProgramSelector& sel) {
+    int64_t hdSidExt = getId(sel, IdentifierType::HD_STATION_ID_EXT, /* defaultValue */ 0);
+    hdSidExt >>= 32;        // Station ID number
+    return hdSidExt & 0xF;  // HD Radio subchannel
+}
+
+bool maybeGetId(const ProgramSelector& sel, const IdentifierType& type, int64_t* val) {
+    // iterate through primaryId and secondaryIds
+    for (auto it = begin(sel); it != end(sel); it++) {
+        if (it->type == type) {
+            if (val != nullptr) {
+                *val = it->value;
+            }
+            return true;
+        }
+    }
+
+    return false;
+}
+
+}  // namespace
+
+IdentifierIterator::IdentifierIterator(const ProgramSelector& sel) : IdentifierIterator(sel, 0) {}
+
+IdentifierIterator::IdentifierIterator(const ProgramSelector& sel, size_t pos)
+    : mSel(sel), mPos(pos) {}
+
+const IdentifierIterator IdentifierIterator::operator++(int) {
+    IdentifierIterator i = *this;
+    mPos++;
+    return i;
+}
+
+IdentifierIterator& IdentifierIterator::operator++() {
+    ++mPos;
+    return *this;
+}
+
+IdentifierIterator::refType IdentifierIterator::operator*() const {
+    if (mPos == 0) {
+        return getSelector().primaryId;
+    }
+
+    // mPos is 1-based for secondary identifiers
+    DCHECK(mPos <= getSelector().secondaryIds.size());
+    return getSelector().secondaryIds[mPos - 1];
+}
+
+bool IdentifierIterator::operator==(const IdentifierIterator& rhs) const {
+    // Check, if both iterators points at the same selector.
+    if (reinterpret_cast<intptr_t>(&getSelector()) !=
+        reinterpret_cast<intptr_t>(&rhs.getSelector())) {
+        return false;
+    }
+
+    return mPos == rhs.mPos;
+}
+
+int32_t resultToInt(Result result) {
+    return static_cast<int32_t>(result);
+}
+
+FrequencyBand getBand(int64_t freq) {
+    // keep in sync with
+    // frameworks/base/services/core/java/com/android/server/broadcastradio/aidl/Utils.java
+    if (freq < 30) return FrequencyBand::UNKNOWN;
+    if (freq < 500) return FrequencyBand::AM_LW;
+    if (freq < 1705) return FrequencyBand::AM_MW;
+    if (freq < 30000) return FrequencyBand::AM_SW;
+    if (freq < 60000) return FrequencyBand::UNKNOWN;
+    if (freq < 110000) return FrequencyBand::FM;
+    return FrequencyBand::UNKNOWN;
+}
+
+bool tunesTo(const ProgramSelector& a, const ProgramSelector& b) {
+    IdentifierType type = b.primaryId.type;
+
+    switch (type) {
+        case IdentifierType::HD_STATION_ID_EXT:
+        case IdentifierType::RDS_PI:
+        case IdentifierType::AMFM_FREQUENCY_KHZ:
+            if (haveEqualIds(a, b, IdentifierType::HD_STATION_ID_EXT)) return true;
+            if (haveEqualIds(a, b, IdentifierType::RDS_PI)) return true;
+            return getHdSubchannel(b) == 0 &&
+                   haveEqualIds(a, b, IdentifierType::AMFM_FREQUENCY_KHZ);
+        case IdentifierType::DAB_SID_EXT:
+            return haveEqualIds(a, b, IdentifierType::DAB_SID_EXT);
+        case IdentifierType::DRMO_SERVICE_ID:
+            return haveEqualIds(a, b, IdentifierType::DRMO_SERVICE_ID);
+        case IdentifierType::SXM_SERVICE_ID:
+            return haveEqualIds(a, b, IdentifierType::SXM_SERVICE_ID);
+        default:  // includes all vendor types
+            LOG(WARNING) << "unsupported program type: " << toString(type);
+            return false;
+    }
+}
+
+bool hasId(const ProgramSelector& sel, const IdentifierType& type) {
+    return maybeGetId(sel, type, /* val */ nullptr);
+}
+
+int64_t getId(const ProgramSelector& sel, const IdentifierType& type) {
+    int64_t val;
+
+    if (maybeGetId(sel, type, &val)) {
+        return val;
+    }
+
+    LOG(WARNING) << "identifier not found: " << toString(type);
+    return kValueForNotFoundIdentifier;
+}
+
+int64_t getId(const ProgramSelector& sel, const IdentifierType& type, int64_t defaultValue) {
+    if (!hasId(sel, type)) {
+        return defaultValue;
+    }
+    return getId(sel, type);
+}
+
+vector<int> getAllIds(const ProgramSelector& sel, const IdentifierType& type) {
+    vector<int> ret;
+
+    // iterate through primaryId and secondaryIds
+    for (auto it = begin(sel); it != end(sel); it++) {
+        if (it->type == type) {
+            ret.push_back(it->value);
+        }
+    }
+
+    return ret;
+}
+
+bool isSupported(const Properties& prop, const ProgramSelector& sel) {
+    for (auto it = prop.supportedIdentifierTypes.begin(); it != prop.supportedIdentifierTypes.end();
+         it++) {
+        if (hasId(sel, *it)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool isValid(const ProgramIdentifier& id) {
+    int64_t val = id.value;
+    bool valid = true;
+
+    auto expect = [&valid](bool condition, const string& message) {
+        if (!condition) {
+            valid = false;
+            LOG(ERROR) << "identifier not valid, expected " << message;
+        }
+    };
+
+    switch (id.type) {
+        case IdentifierType::INVALID:
+            expect(false, "IdentifierType::INVALID");
+            break;
+        case IdentifierType::DAB_FREQUENCY_KHZ:
+            expect(val > 100000u, "f > 100MHz");
+            [[fallthrough]];
+        case IdentifierType::AMFM_FREQUENCY_KHZ:
+        case IdentifierType::DRMO_FREQUENCY_KHZ:
+            expect(val > 100u, "f > 100kHz");
+            expect(val < 10000000u, "f < 10GHz");
+            break;
+        case IdentifierType::RDS_PI:
+            expect(val != 0u, "RDS PI != 0");
+            expect(val <= 0xFFFFu, "16bit id");
+            break;
+        case IdentifierType::HD_STATION_ID_EXT: {
+            int64_t stationId = val & 0xFFFFFFFF;  // 32bit
+            val >>= 32;
+            int64_t subchannel = val & 0xF;  // 4bit
+            val >>= 4;
+            int64_t freq = val & 0x3FFFF;  // 18bit
+            expect(stationId != 0u, "HD station id != 0");
+            expect(subchannel < 8u, "HD subch < 8");
+            expect(freq > 100u, "f > 100kHz");
+            expect(freq < 10000000u, "f < 10GHz");
+            break;
+        }
+        case IdentifierType::HD_STATION_NAME: {
+            while (val > 0) {
+                char ch = static_cast<char>(val & 0xFF);
+                val >>= 8;
+                expect((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'),
+                       "HD_STATION_NAME does not match [A-Z0-9]+");
+            }
+            break;
+        }
+        case IdentifierType::DAB_SID_EXT: {
+            int64_t sid = val & 0xFFFF;  // 16bit
+            val >>= 16;
+            int64_t ecc = val & 0xFF;  // 8bit
+            expect(sid != 0u, "DAB SId != 0");
+            expect(ecc >= 0xA0u && ecc <= 0xF6u, "Invalid ECC, see ETSI TS 101 756 V2.1.1");
+            break;
+        }
+        case IdentifierType::DAB_ENSEMBLE:
+            expect(val != 0u, "DAB ensemble != 0");
+            expect(val <= 0xFFFFu, "16bit id");
+            break;
+        case IdentifierType::DAB_SCID:
+            expect(val > 0xFu, "12bit SCId (not 4bit SCIdS)");
+            expect(val <= 0xFFFu, "12bit id");
+            break;
+        case IdentifierType::DRMO_SERVICE_ID:
+            expect(val != 0u, "DRM SId != 0");
+            expect(val <= 0xFFFFFFu, "24bit id");
+            break;
+        case IdentifierType::SXM_SERVICE_ID:
+            expect(val != 0u, "SXM SId != 0");
+            expect(val <= 0xFFFFFFFFu, "32bit id");
+            break;
+        case IdentifierType::SXM_CHANNEL:
+            expect(val < 1000u, "SXM channel < 1000");
+            break;
+        case IdentifierType::VENDOR_START:
+        case IdentifierType::VENDOR_END:
+            // skip
+            break;
+    }
+
+    return valid;
+}
+
+bool isValid(const ProgramSelector& sel) {
+    // iterate through primaryId and secondaryIds
+    for (auto it = begin(sel); it != end(sel); it++) {
+        if (!isValid(*it)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+ProgramIdentifier makeIdentifier(IdentifierType type, int64_t value) {
+    return {type, value};
+}
+
+ProgramSelector makeSelectorAmfm(int32_t frequency) {
+    ProgramSelector sel = {};
+    sel.primaryId = makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, frequency);
+    return sel;
+}
+
+ProgramSelector makeSelectorDab(int32_t sidExt, int32_t ensemble) {
+    ProgramSelector sel = {};
+    // TODO(243686545): Have a helper function to create the sidExt instead of
+    // passing the whole identifier here. Something like makeDabSidExt.
+    sel.primaryId = makeIdentifier(IdentifierType::DAB_SID_EXT, sidExt);
+    vector<ProgramIdentifier> secondaryIds = {
+            makeIdentifier(IdentifierType::DAB_ENSEMBLE, ensemble),
+            // TODO(243686545): Include frequency here when the helper method to
+            // translate between ensemble and frequency is implemented.
+    };
+    sel.secondaryIds = std::move(secondaryIds);
+    return sel;
+}
+
+bool satisfies(const ProgramFilter& filter, const ProgramSelector& sel) {
+    if (filter.identifierTypes.size() > 0) {
+        auto typeEquals = [](const ProgramIdentifier& id, IdentifierType type) {
+            return id.type == type;
+        };
+        auto it = std::find_first_of(begin(sel), end(sel), filter.identifierTypes.begin(),
+                                     filter.identifierTypes.end(), typeEquals);
+        if (it == end(sel)) {
+            return false;
+        }
+    }
+
+    if (filter.identifiers.size() > 0) {
+        auto it = std::find_first_of(begin(sel), end(sel), filter.identifiers.begin(),
+                                     filter.identifiers.end());
+        if (it == end(sel)) {
+            return false;
+        }
+    }
+
+    if (!filter.includeCategories && sel.primaryId.type == IdentifierType::DAB_ENSEMBLE) {
+        return false;
+    }
+
+    return true;
+}
+
+size_t ProgramInfoHasher::operator()(const ProgramInfo& info) const {
+    const ProgramIdentifier& id = info.selector.primaryId;
+
+    // This is not the best hash implementation, but good enough for default HAL
+    // implementation and tests.
+    size_t h = 0;
+    ::android::hashCombineSingle(h, id.type);
+    ::android::hashCombineSingle(h, id.value);
+    return h;
+}
+
+bool ProgramInfoKeyEqual::operator()(const ProgramInfo& info1, const ProgramInfo& info2) const {
+    const ProgramIdentifier& id1 = info1.selector.primaryId;
+    const ProgramIdentifier& id2 = info2.selector.primaryId;
+    return id1.type == id2.type && id1.value == id2.value;
+}
+
+void updateProgramList(const ProgramListChunk& chunk, ProgramInfoSet* list) {
+    if (chunk.purge) {
+        list->clear();
+    }
+
+    list->insert(chunk.modified.begin(), chunk.modified.end());
+
+    if (!chunk.removed.has_value()) {
+        return;
+    }
+
+    for (auto& id : chunk.removed.value()) {
+        if (id.has_value()) {
+            ProgramInfo info = {};
+            info.selector.primaryId = id.value();
+            list->erase(info);
+        }
+    }
+}
+
+std::optional<std::string> getMetadataString(const ProgramInfo& info, const Metadata::Tag& tag) {
+    auto isRdsPs = [tag](const Metadata& item) { return item.getTag() == tag; };
+
+    auto it = std::find_if(info.metadata.begin(), info.metadata.end(), isRdsPs);
+    if (it == info.metadata.end()) {
+        return std::nullopt;
+    }
+
+    std::string metadataString;
+    switch (it->getTag()) {
+        case Metadata::rdsPs:
+            metadataString = it->get<Metadata::rdsPs>();
+            break;
+        case Metadata::rdsPty:
+            metadataString = std::to_string(it->get<Metadata::rdsPty>());
+            break;
+        case Metadata::rbdsPty:
+            metadataString = std::to_string(it->get<Metadata::rbdsPty>());
+            break;
+        case Metadata::rdsRt:
+            metadataString = it->get<Metadata::rdsRt>();
+            break;
+        case Metadata::songTitle:
+            metadataString = it->get<Metadata::songTitle>();
+            break;
+        case Metadata::songArtist:
+            metadataString = it->get<Metadata::songArtist>();
+            break;
+        case Metadata::songAlbum:
+            metadataString = it->get<Metadata::songAlbum>();
+            break;
+        case Metadata::stationIcon:
+            metadataString = std::to_string(it->get<Metadata::stationIcon>());
+            break;
+        case Metadata::albumArt:
+            metadataString = std::to_string(it->get<Metadata::albumArt>());
+            break;
+        case Metadata::programName:
+            metadataString = it->get<Metadata::programName>();
+            break;
+        case Metadata::dabEnsembleName:
+            metadataString = it->get<Metadata::dabEnsembleName>();
+            break;
+        case Metadata::dabEnsembleNameShort:
+            metadataString = it->get<Metadata::dabEnsembleNameShort>();
+            break;
+        case Metadata::dabServiceName:
+            metadataString = it->get<Metadata::dabServiceName>();
+            break;
+        case Metadata::dabServiceNameShort:
+            metadataString = it->get<Metadata::dabServiceNameShort>();
+            break;
+        case Metadata::dabComponentName:
+            metadataString = it->get<Metadata::dabComponentName>();
+            break;
+        case Metadata::dabComponentNameShort:
+            metadataString = it->get<Metadata::dabComponentNameShort>();
+            break;
+        default:
+            LOG(ERROR) << "Metadata " << it->toString() << " is not converted.";
+            return std::nullopt;
+    }
+    return metadataString;
+}
+
+ProgramIdentifier makeHdRadioStationName(const string& name) {
+    constexpr size_t maxlen = 8;
+
+    string shortName;
+    shortName.reserve(maxlen);
+
+    const auto& loc = std::locale::classic();
+    for (const char& ch : name) {
+        if (!std::isalnum(ch, loc)) {
+            continue;
+        }
+        shortName.push_back(std::toupper(ch, loc));
+        if (shortName.length() >= maxlen) {
+            break;
+        }
+    }
+
+    // Short name is converted to HD_STATION_NAME by encoding each char into its ASCII value in
+    // in little-endian order. For example, "Abc" is converted to 0x434241.
+    int64_t val = 0;
+    for (auto rit = shortName.rbegin(); rit != shortName.rend(); ++rit) {
+        val <<= 8;
+        val |= static_cast<char>(*rit);
+    }
+
+    return makeIdentifier(IdentifierType::HD_STATION_NAME, val);
+}
+
+IdentifierType getType(int typeAsInt) {
+    return static_cast<IdentifierType>(typeAsInt);
+}
+
+bool parseArgInt(const string& s, int* out) {
+    return ::android::base::ParseInt(s, out);
+}
+
+bool parseArgLong(const std::string& s, long* out) {
+    return ::android::base::ParseInt(s, out);
+}
+
+bool parseArgBool(const string& s, bool* out) {
+    if (EqualsIgnoreCase(s, "true")) {
+        *out = true;
+    } else if (EqualsIgnoreCase(s, "false")) {
+        *out = false;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+bool parseArgDirection(const string& s, bool* out) {
+    if (EqualsIgnoreCase(s, "up")) {
+        *out = true;
+    } else if (EqualsIgnoreCase(s, "down")) {
+        *out = false;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+bool parseArgIdentifierTypeArray(const string& s, vector<IdentifierType>* out) {
+    for (const string& val : ::android::base::Split(s, ",")) {
+        int outInt;
+        if (!parseArgInt(val, &outInt)) {
+            return false;
+        }
+        out->push_back(getType(outInt));
+    }
+    return true;
+}
+
+bool parseProgramIdentifierList(const std::string& s, vector<ProgramIdentifier>* out) {
+    for (const string& idStr : ::android::base::Split(s, ",")) {
+        const vector<string> idStrPair = ::android::base::Split(idStr, ":");
+        if (idStrPair.size() != 2) {
+            return false;
+        }
+        int idType;
+        if (!parseArgInt(idStrPair[0], &idType)) {
+            return false;
+        }
+        long idVal;
+        if (!parseArgLong(idStrPair[1], &idVal)) {
+            return false;
+        }
+        ProgramIdentifier id = {getType(idType), idVal};
+        out->push_back(id);
+    }
+    return true;
+}
+
+}  // namespace utils
+
+utils::IdentifierIterator begin(const ProgramSelector& sel) {
+    return utils::IdentifierIterator(sel);
+}
+
+utils::IdentifierIterator end(const ProgramSelector& sel) {
+    return utils::IdentifierIterator(sel) + 1 /* primary id */ + sel.secondaryIds.size();
+}
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
new file mode 100644
index 0000000..8ea6319
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
+#include <aidl/android/hardware/broadcastradio/Metadata.h>
+#include <aidl/android/hardware/broadcastradio/ProgramFilter.h>
+#include <aidl/android/hardware/broadcastradio/ProgramIdentifier.h>
+#include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
+#include <aidl/android/hardware/broadcastradio/ProgramListChunk.h>
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+#include <aidl/android/hardware/broadcastradio/Properties.h>
+#include <aidl/android/hardware/broadcastradio/Result.h>
+
+#include <numeric>
+#include <optional>
+#include <thread>
+#include <unordered_set>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+enum class FrequencyBand {
+    UNKNOWN,
+    FM,
+    AM_LW,
+    AM_MW,
+    AM_SW,
+};
+
+class IdentifierIterator final
+    : public std::iterator<std::random_access_iterator_tag, ProgramIdentifier, ssize_t,
+                           const ProgramIdentifier*, const ProgramIdentifier&> {
+    using ptrType = typename std::iterator_traits<IdentifierIterator>::pointer;
+    using refType = typename std::iterator_traits<IdentifierIterator>::reference;
+    using diffType = typename std::iterator_traits<IdentifierIterator>::difference_type;
+
+  public:
+    explicit IdentifierIterator(const ProgramSelector& sel);
+
+    const IdentifierIterator operator++(int);
+    IdentifierIterator& operator++();
+    refType operator*() const;
+    inline ptrType operator->() const { return &operator*(); }
+    IdentifierIterator operator+(diffType v) const { return IdentifierIterator(mSel, mPos + v); }
+    bool operator==(const IdentifierIterator& rhs) const;
+    inline bool operator!=(const IdentifierIterator& rhs) const { return !operator==(rhs); };
+
+  private:
+    explicit IdentifierIterator(const ProgramSelector& sel, size_t pos);
+
+    std::reference_wrapper<const ProgramSelector> mSel;
+
+    const ProgramSelector& getSelector() const { return mSel.get(); }
+
+    /** 0 is the primary identifier, 1-n are secondary identifiers. */
+    size_t mPos = 0;
+};
+
+/**
+ * Convert Result to int
+ */
+int32_t resultToInt(Result result);
+
+/**
+ * Guesses band from the frequency value.
+ *
+ * The band bounds are not exact to cover multiple regions.
+ * The function is biased towards success, i.e. it never returns
+ * FrequencyBand::UNKNOWN for correct frequency, but a result for
+ * incorrect one is undefined (it doesn't have to return UNKNOWN).
+ */
+FrequencyBand getBand(int64_t frequency);
+
+/**
+ * Checks, if {@code pointer} tunes to {@channel}.
+ *
+ * For example, having a channel {AMFM_FREQUENCY_KHZ = 103.3}:
+ * - selector {AMFM_FREQUENCY_KHZ = 103.3, HD_SUBCHANNEL = 0} can tune to this channel;
+ * - selector {AMFM_FREQUENCY_KHZ = 103.3, HD_SUBCHANNEL = 1} can't.
+ *
+ * @param pointer selector we're trying to match against channel.
+ * @param channel existing channel.
+ */
+bool tunesTo(const ProgramSelector& pointer, const ProgramSelector& channel);
+
+/**
+ * Checks whether a given program selector has the given ID (either primary or secondary).
+ */
+bool hasId(const ProgramSelector& sel, const IdentifierType& type);
+
+/**
+ * Returns ID (either primary or secondary) for a given program selector.
+ *
+ * If the selector does not contain given type, returns kValueForNotFoundIdentifier
+ * and emits a warning.
+ */
+int64_t getId(const ProgramSelector& sel, const IdentifierType& type);
+
+/**
+ * Returns ID (either primary or secondary) for a given program selector.
+ *
+ * If the selector does not contain given type, returns default value.
+ */
+int64_t getId(const ProgramSelector& sel, const IdentifierType& type, int64_t defaultValue);
+
+/**
+ * Returns all IDs of a given type.
+ */
+std::vector<int> getAllIds(const ProgramSelector& sel, const IdentifierType& type);
+
+/**
+ * Checks, if a given selector is supported by the radio module.
+ *
+ * @param prop Module description.
+ * @param sel The selector to check.
+ * @return True, if the selector is supported, false otherwise.
+ */
+bool isSupported(const Properties& prop, const ProgramSelector& sel);
+
+bool isValid(const ProgramIdentifier& id);
+bool isValid(const ProgramSelector& sel);
+
+ProgramIdentifier makeIdentifier(IdentifierType type, int64_t value);
+ProgramSelector makeSelectorAmfm(int32_t frequency);
+ProgramSelector makeSelectorDab(int32_t sidExt, int32_t ensemble);
+
+bool satisfies(const ProgramFilter& filter, const ProgramSelector& sel);
+
+struct ProgramInfoHasher {
+    size_t operator()(const ProgramInfo& info) const;
+};
+
+struct ProgramInfoKeyEqual {
+    bool operator()(const ProgramInfo& info1, const ProgramInfo& info2) const;
+};
+
+typedef std::unordered_set<ProgramInfo, ProgramInfoHasher, ProgramInfoKeyEqual> ProgramInfoSet;
+
+void updateProgramList(const ProgramListChunk& chunk, ProgramInfoSet* list);
+
+std::optional<std::string> getMetadataString(const ProgramInfo& info, const Metadata::Tag& tag);
+
+ProgramIdentifier makeHdRadioStationName(const std::string& name);
+
+template <typename aidl_type>
+inline std::string vectorToString(const std::vector<aidl_type>& in_values) {
+    return std::accumulate(std::begin(in_values), std::end(in_values), std::string{},
+                           [](const std::string& ls, const aidl_type& rs) {
+                               return ls + (ls.empty() ? "" : ",") + toString(rs);
+                           });
+}
+
+IdentifierType getType(int typeAsInt);
+
+bool parseArgInt(const std::string& s, int* out);
+
+bool parseArgLong(const std::string& s, long* out);
+
+bool parseArgBool(const std::string& s, bool* out);
+
+bool parseArgDirection(const std::string& s, bool* out);
+
+bool parseArgIdentifierTypeArray(const std::string& s, std::vector<IdentifierType>* out);
+
+bool parseProgramIdentifierList(const std::string& s, std::vector<ProgramIdentifier>* out);
+
+}  // namespace utils
+
+utils::IdentifierIterator begin(const ProgramSelector& sel);
+utils::IdentifierIterator end(const ProgramSelector& sel);
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
index 6866776..b0ae20e 100644
--- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
+++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp
@@ -7883,6 +7883,7 @@
             }
         }
 
+        camera_metadata_t* staticMetadata;
         camera_metadata_ro_entry physicalMultiResStreamConfigs;
         camera_metadata_ro_entry physicalStreamConfigs;
         camera_metadata_ro_entry physicalMaxResolutionStreamConfigs;
@@ -7901,8 +7902,9 @@
 
             ret = subDevice->getCameraCharacteristics([&](auto status, const auto& chars) {
                 ASSERT_EQ(Status::OK, status);
-                const camera_metadata_t* staticMetadata =
-                        reinterpret_cast<const camera_metadata_t*>(chars.data());
+                staticMetadata = clone_camera_metadata(
+                        reinterpret_cast<const camera_metadata_t*>(chars.data()));
+                ASSERT_NE(nullptr, staticMetadata);
                 rc = getSystemCameraKind(staticMetadata, &physSystemCameraKind);
                 ASSERT_EQ(rc, Status::OK);
                 // Make sure that the system camera kind of a non-hidden
@@ -7936,7 +7938,9 @@
                         verifyCameraCharacteristics(status, chars);
                         verifyMonochromeCharacteristics(chars, deviceVersion);
 
-                        auto staticMetadata = (const camera_metadata_t*)chars.data();
+                        staticMetadata = clone_camera_metadata(
+                                reinterpret_cast<const camera_metadata_t*>(chars.data()));
+                        ASSERT_NE(nullptr, staticMetadata);
                         retcode = find_camera_metadata_ro_entry(
                                 staticMetadata, ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry);
                         bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2);
@@ -8064,6 +8068,7 @@
                 }
             }
         }
+        free_camera_metadata(staticMetadata);
     }
 
     // If a multi-resolution stream is supported, there must be at least one
diff --git a/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp b/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
index 70ab7a0..3fef089 100644
--- a/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
+++ b/camera/provider/aidl/vts/VtsAidlHalCameraProvider_TargetTest.cpp
@@ -386,6 +386,22 @@
                 mTorchStatus = TorchModeStatus::NOT_AVAILABLE;
             }
 
+            // register a new callback; make sure it receives the
+            // flash-on callback.
+            std::shared_ptr<TorchProviderCb> cb2 = ndk::SharedRefBase::make<TorchProviderCb>(this);
+            ret = mProvider->setCallback(cb2);
+            ASSERT_TRUE(ret.isOk());
+            ASSERT_NE(cb2, nullptr);
+            {
+                std::unique_lock<std::mutex> l(mTorchLock);
+                while (TorchModeStatus::NOT_AVAILABLE == mTorchStatus) {
+                    auto timeout = std::chrono::system_clock::now() +
+                                   std::chrono::seconds(kTorchTimeoutSec);
+                    ASSERT_NE(std::cv_status::timeout, mTorchCond.wait_until(l, timeout));
+                }
+                ASSERT_EQ(TorchModeStatus::AVAILABLE_ON, mTorchStatus);
+            }
+
             ret = device->setTorchMode(false);
             ASSERT_TRUE(ret.isOk());
             {
diff --git a/camera/provider/aidl/vts/camera_aidl_test.cpp b/camera/provider/aidl/vts/camera_aidl_test.cpp
index 20f32bf..2c2f1b2 100644
--- a/camera/provider/aidl/vts/camera_aidl_test.cpp
+++ b/camera/provider/aidl/vts/camera_aidl_test.cpp
@@ -1181,6 +1181,7 @@
         camera_metadata_ro_entry physicalMultiResStreamConfigs;
         camera_metadata_ro_entry physicalStreamConfigs;
         camera_metadata_ro_entry physicalMaxResolutionStreamConfigs;
+        CameraMetadata physChars;
         bool isUltraHighRes = false;
         std::unordered_set<int32_t> subCameraPrivacyTestPatterns;
         if (isPublicId) {
@@ -1189,12 +1190,11 @@
             ASSERT_TRUE(ret.isOk());
             ASSERT_NE(subDevice, nullptr);
 
-            CameraMetadata subDeviceChars;
-            ret = subDevice->getCameraCharacteristics(&subDeviceChars);
+            ret = subDevice->getCameraCharacteristics(&physChars);
             ASSERT_TRUE(ret.isOk());
 
             const camera_metadata_t* staticMetadata =
-                    reinterpret_cast<const camera_metadata_t*>(subDeviceChars.metadata.data());
+                    reinterpret_cast<const camera_metadata_t*>(physChars.metadata.data());
             retStatus = getSystemCameraKind(staticMetadata, &physSystemCameraKind);
             ASSERT_EQ(retStatus, Status::OK);
 
@@ -1215,7 +1215,6 @@
             getPrivacyTestPatternModes(staticMetadata, &subCameraPrivacyTestPatterns);
         } else {
             // Check camera characteristics for hidden camera id
-            CameraMetadata physChars;
             ndk::ScopedAStatus ret =
                     device->getPhysicalCameraCharacteristics(physicalId, &physChars);
             ASSERT_TRUE(ret.isOk());
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 3d90289..63e9406 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -16,7 +16,7 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="false">
+    <hal format="hidl" optional="true">
         <name>android.hardware.audio.effect</name>
         <version>6.0</version>
         <version>7.0</version>
@@ -37,7 +37,7 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="aidl" optional="false">
+    <hal format="aidl" optional="true">
         <name>android.hardware.audio.effect</name>
         <version>1</version>
         <interface>
@@ -112,6 +112,13 @@
             <regex-instance>.*</regex-instance>
         </interface>
     </hal>
+     <hal format="aidl" optional="true">
+        <name>android.hardware.automotive.remoteaccess</name>
+        <interface>
+            <name>IRemoteAccess</name>
+            <regex-instance>.*</regex-instance>
+        </interface>
+    </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.biometrics.face</name>
         <version>2</version>
@@ -152,17 +159,8 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="true">
+    <hal format="aidl" optional="true">
         <name>android.hardware.broadcastradio</name>
-        <version>1.0-1</version>
-        <interface>
-            <name>IBroadcastRadioFactory</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
-    <hal format="hidl" optional="true">
-        <name>android.hardware.broadcastradio</name>
-        <version>2.0</version>
         <interface>
             <name>IBroadcastRadio</name>
             <regex-instance>.*</regex-instance>
@@ -201,6 +199,14 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true">
+        <name>android.hardware.confirmationui</name>
+        <version>1</version>
+        <interface>
+            <name>IConfirmationUI</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
         <name>android.hardware.contexthub</name>
         <interface>
             <name>IContextHub</name>
@@ -222,7 +228,7 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <hal format="hidl" optional="false">
+    <hal format="hidl" optional="true">
         <name>android.hardware.gatekeeper</name>
         <version>1.0</version>
         <interface>
@@ -231,6 +237,14 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true">
+        <name>android.hardware.gatekeeper</name>
+        <version>1</version>
+        <interface>
+            <name>IGatekeeper</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
         <name>android.hardware.gnss</name>
         <version>2</version>
         <interface>
@@ -432,7 +446,7 @@
     </hal>
     <hal format="aidl" optional="false">
         <name>android.hardware.power</name>
-        <version>2-3</version>
+        <version>2-4</version>
         <interface>
             <name>IPower</name>
             <instance>default</instance>
@@ -485,7 +499,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.radio.network</name>
-        <version>1-2</version>
+        <version>2</version>
         <interface>
             <name>IRadioNetwork</name>
             <instance>slot1</instance>
diff --git a/confirmationui/OWNERS b/confirmationui/OWNERS
new file mode 100644
index 0000000..2bcdb0e
--- /dev/null
+++ b/confirmationui/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+subrahmanyaman@google.com
diff --git a/confirmationui/aidl/Android.bp b/confirmationui/aidl/Android.bp
new file mode 100644
index 0000000..4ba4acd
--- /dev/null
+++ b/confirmationui/aidl/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.hardware.confirmationui",
+    vendor_available: true,
+    imports: [
+        "android.hardware.security.keymint-V2",
+    ],
+    srcs: ["android/hardware/confirmationui/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            apps_enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl
index 918ae3a..6d6ec97 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationResultCallback.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,8 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.confirmationui;
+@VintfStability
+interface IConfirmationResultCallback {
+  void result(in int error, in byte[] formattedMessage, in byte[] confirmationToken);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl
similarity index 65%
copy from radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
copy to confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl
index 930ff54..c5c7aa7 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/IConfirmationUI.aidl
@@ -31,11 +31,24 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.network;
+package android.hardware.confirmationui;
 @VintfStability
-parcelable NrRegistrationInfo {
-  android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
-  int extraInfo;
-  const int EXTRA_SMS_OVER_NAS_ALLOWED = 1;
-  const int EXTRA_REGISTERED_FOR_EMERGENCY = 2;
+interface IConfirmationUI {
+  void abort();
+  void deliverSecureInputEvent(in android.hardware.security.keymint.HardwareAuthToken secureInputToken);
+  void promptUserConfirmation(in android.hardware.confirmationui.IConfirmationResultCallback resultCB, in byte[] promptText, in byte[] extraData, in @utf8InCpp String locale, in android.hardware.confirmationui.UIOption[] uiOptions);
+  const int OK = 0;
+  const int CANCELED = 1;
+  const int ABORTED = 2;
+  const int OPERATION_PENDING = 3;
+  const int IGNORED = 4;
+  const int SYSTEM_ERROR = 5;
+  const int UNIMPLEMENTED = 6;
+  const int UNEXPECTED = 7;
+  const int UI_ERROR = 65536;
+  const int UI_ERROR_MISSING_GLYPH = 65537;
+  const int UI_ERROR_MESSAGE_TOO_LONG = 65538;
+  const int UI_ERROR_MALFORMED_UTF8ENCODING = 65539;
+  const int TEST_KEY_BYTE = 165;
+  const int MAX_MESSAGE_SIZE = 6144;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl
index 918ae3a..b8629d1 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/TestModeCommands.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.confirmationui;
+@Backing(type="int") @VintfStability
+enum TestModeCommands {
+  OK_EVENT = 0,
+  CANCEL_EVENT = 1,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl
index 918ae3a..ebecf9e 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/confirmationui/aidl/aidl_api/android.hardware.confirmationui/current/android/hardware/confirmationui/UIOption.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,9 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.confirmationui;
+@Backing(type="int") @VintfStability
+enum UIOption {
+  ACCESSIBILITY_INVERTED = 0,
+  ACCESSIBILITY_MAGNIFIED = 1,
 }
diff --git a/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl
new file mode 100644
index 0000000..2165fdd
--- /dev/null
+++ b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationResultCallback.aidl
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.confirmationui;
+
+import android.hardware.confirmationui.IConfirmationUI;
+
+/**
+ * Callback interface passed to IConfirmationUI::promptUserConfirmation().
+ * Informs the caller about the result of the prompt operation.
+ */
+@VintfStability
+interface IConfirmationResultCallback {
+    /**
+     * This callback is called by the confirmation provider when it stops prompting the user.
+     * Iff the user has confirmed the prompted text, error is ErrorCode::OK and the
+     * parameters formattedMessage and confirmationToken hold the values needed to request
+     * a signature from keymaster.
+     * In all other cases formattedMessage and confirmationToken must be of length 0.
+     *
+     * @param error - OK: IFF the user has confirmed the prompt.
+     *              - CANCELED: If the user has pressed the cancel button.
+     *              - ABORTED: If IConfirmationUI::abort() was called.
+     *              - SYSTEM_ERROR: If an unexpected System error occurred that prevented the TUI
+     *                             from being shut down gracefully.
+     *
+     * @param formattedMessage holds the prompt text and extra data.
+     *                         The message is CBOR (RFC 7049) encoded and has the following format:
+     *                         CBOR_MAP{ "prompt", <promptText>, "extra", <extraData> }
+     *                         The message is a CBOR encoded map (type 5) with the keys
+     *                         "prompt" and "extra". The keys are encoded as CBOR text string
+     *                         (type 3). The value <promptText> is encoded as CBOR text string
+     *                         (type 3), and the value <extraData> is encoded as CBOR byte string
+     *                         (type 2). The map must have exactly one key value pair for each of
+     *                         the keys "prompt" and "extra". Other keys are not allowed.
+     *                         The value of "prompt" is given by the proptText argument to
+     *                         IConfirmationUI::promptUserConfirmation and must not be modified
+     *                         by the implementation.
+     *                         The value of "extra" is given by the extraData argument to
+     *                         IConfirmationUI::promptUserConfirmation and must not be modified
+     *                         or interpreted by the implementation.
+     *
+     * @param confirmationToken a 32-byte HMAC-SHA256 value, computed over
+     *                          "confirmation token" || <formattedMessage>
+     *                          i.e. the literal UTF-8 encoded string "confirmation token", without
+     *                          the "", concatenated with the formatted message as returned in the
+     *                          formattedMessage argument. The HMAC is keyed with a 256-bit secret
+     *                          which is shared with Keymaster. In test mode the test key MUST be
+     *                          used (see types.hal TestModeCommands and
+     * IConfirmationUI::TEST_KEY_BYTE).
+     */
+    void result(in int error, in byte[] formattedMessage, in byte[] confirmationToken);
+}
diff --git a/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl
new file mode 100644
index 0000000..f071126
--- /dev/null
+++ b/confirmationui/aidl/android/hardware/confirmationui/IConfirmationUI.aidl
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.confirmationui;
+
+import android.hardware.confirmationui.IConfirmationResultCallback;
+import android.hardware.confirmationui.UIOption;
+import android.hardware.security.keymint.HardwareAuthToken;
+
+@VintfStability
+interface IConfirmationUI {
+    // Possible Errors.
+    /**
+     * API call succeeded or the user gave approval (result callback).
+     */
+    const int OK = 0;
+    /**
+     * The user canceled the TUI (result callback).
+     */
+    const int CANCELED = 1;
+    /**
+     * IConfirmationUI::abort() was called. (result callback).
+     */
+    const int ABORTED = 2;
+    /**
+     * Cannot start another prompt.
+     */
+    const int OPERATION_PENDING = 3;
+    /**
+     * IConfirmationUI::deliverSecureInputEvent call was ignored.
+     */
+    const int IGNORED = 4;
+    /**
+     * An unexpected system error occurred.
+     */
+    const int SYSTEM_ERROR = 5;
+    /**
+     * Returned by an unimplemented API call.
+     */
+    const int UNIMPLEMENTED = 6;
+    /**
+     * This is returned when an error is diagnosed that should have been
+     * caught by earlier input sanitization. Should never be seen in production.
+     */
+    const int UNEXPECTED = 7;
+    /**
+     * General UI error.
+     */
+    const int UI_ERROR = 0x10000;
+    /**
+     * This is returned if there is no corresponding glyph for a character.
+     */
+    const int UI_ERROR_MISSING_GLYPH = 0x10001;
+    /**
+     * The implementation must return this error code on promptUserConfirmation if the
+     * resulting formatted message does not fit into MAX_MESSAGE_SIZE bytes. It is
+     * advised that the implementation formats the message upon receiving this API call to
+     * be able to diagnose this syndrome.
+     */
+    const int UI_ERROR_MESSAGE_TOO_LONG = 0x10002;
+    /**
+     * This is returned if the UTF8 encoding is malformed.
+     */
+    const int UI_ERROR_MALFORMED_UTF8ENCODING = 0x10003;
+
+    // Constants
+    /**
+     * The test key is 32byte word with all bytes set to TEST_KEY_BYTE.
+     */
+    const int TEST_KEY_BYTE = 0xA5;
+    /**
+     * This defines the maximum message size. This indirectly limits the size of the prompt text
+     * and the extra data that can be passed to the confirmation UI. The prompt text and extra data
+     * must fit in to this size including CBOR header information.
+     */
+    const int MAX_MESSAGE_SIZE = 0x1800;
+
+    /**
+     * Aborts a pending user prompt. This allows the framework to gracefully end a TUI dialog.
+     * If a TUI operation was pending the corresponding call back is informed with
+     * ErrorCode::Aborted.
+     */
+    void abort();
+
+    /**
+     * DeliverSecureInput is used by the framework to deliver a secure input event to the
+     * confirmation provider.
+     *
+     * VTS test mode:
+     * This function can be used to test certain code paths non-interactively.
+     * See TestModeCommands.aidl for details.
+     *
+     * @param secureInputToken An authentication token as generated by Android authentication
+     *                         providers.
+     */
+    void deliverSecureInputEvent(in HardwareAuthToken secureInputToken);
+
+    /**
+     * Asynchronously initiates a confirmation UI dialog prompting the user to confirm a given text.
+     * The TUI prompt must be implemented in such a way that a positive response indicates with
+     * high confidence that a user has seen the given prompt text even if the Android framework
+     * including the kernel was compromised.
+     *
+     * Service status return:
+     *
+     * OK IFF the dialog was successfully started. In this case, and only in this
+     *                      case, the implementation must, eventually, call the callback to
+     *                      indicate completion.
+     * OPERATION_PENDING is returned when the confirmation provider is currently
+     *                      in use.
+     * SYSTEM_ERROR an error occurred trying to communicate with the confirmation
+     *                      provider (e.g. trusted app).
+     * UI_ERROR the confirmation provider encountered an issue with displaying
+     *                      the prompt text to the user.
+     *
+     * @param resultCB Implementation of IResultCallback. Used by the implementation to report
+     *                 the result of the current pending user prompt.
+     *
+     * @param promptText UTF-8 encoded bytes which is to be presented to the user.
+     *
+     * @param extraData A binary blob that must be included in the formatted output message as is.
+     *                  It is opaque to the implementation. Implementations must neither interpret
+     *                  nor modify the content.
+     *
+     * @param locale String specifying the locale that must be used by the TUI dialog. The string
+     *                      is an IETF BCP 47 tag.
+     *
+     * @param uiOptions A set of uiOptions manipulating how the confirmation prompt is displayed.
+     *                  Refer to UIOption in types.hal for possible options.
+     */
+    void promptUserConfirmation(in IConfirmationResultCallback resultCB, in byte[] promptText,
+            in byte[] extraData, in @utf8InCpp String locale, in UIOption[] uiOptions);
+}
diff --git a/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl b/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl
new file mode 100644
index 0000000..5b1d8fb
--- /dev/null
+++ b/confirmationui/aidl/android/hardware/confirmationui/TestModeCommands.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.confirmationui;
+
+/**
+ * Test mode commands.
+ *
+ * IConfirmationUI::deliverSecureInputEvent can be used to test certain code paths.
+ * To that end, the caller passes an auth token that has an HMAC keyed with the test key
+ * (see IConfirmationUI::TEST_KEY_BYTE). Implementations first check the HMAC against test key.
+ * If the test key produces a matching HMAC, the implementation evaluates the challenge field
+ * of the auth token against the values defined in TestModeCommand.
+ * If the command indicates that a confirmation token is to be generated the test key MUST be used
+ * to generate this confirmation token.
+ *
+ * See command code for individual test command descriptions.
+ */
+@VintfStability
+@Backing(type="int")
+enum TestModeCommands {
+    /**
+     * Simulates the user pressing the OK button on the UI. If no operation is pending
+     * ResponseCode::Ignored must be returned. A pending operation is finalized successfully
+     * see IConfirmationResultCallback::result, however, the test key
+     * (see IConfirmationUI::TEST_KEY_BYTE) MUST be used to generate the confirmation token.
+     */
+    OK_EVENT = 0,
+    /**
+     * Simulates the user pressing the CANCEL button on the UI. If no operation is pending
+     * Result::Ignored must be returned. A pending operation is finalized as specified in
+     * IConfirmationResultCallback.hal.
+     */
+    CANCEL_EVENT = 1,
+}
diff --git a/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl b/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl
new file mode 100644
index 0000000..b242c53
--- /dev/null
+++ b/confirmationui/aidl/android/hardware/confirmationui/UIOption.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.confirmationui;
+
+/**
+ * UI modification options.
+ */
+@VintfStability
+@Backing(type="int")
+enum UIOption {
+    /**
+     * Accessibility: Requests color inverted style.
+     */
+    ACCESSIBILITY_INVERTED = 0,
+    /**
+     * Accessibility: Requests magnified style.
+     */
+    ACCESSIBILITY_MAGNIFIED = 1,
+}
diff --git a/confirmationui/aidl/vts/functional/Android.bp b/confirmationui/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..3649c87
--- /dev/null
+++ b/confirmationui/aidl/vts/functional/Android.bp
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsHalConfirmationUITargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "keymint_use_latest_hal_aidl_ndk_shared",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: [
+        "VtsHalConfirmationUITargetTest.cpp",
+    ],
+    static_libs: [
+        "android.hardware.confirmationui-V1-ndk",
+        "libcn-cbor",
+        "android.hardware.confirmationui-support-lib",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp b/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp
new file mode 100644
index 0000000..bf1f1c8
--- /dev/null
+++ b/confirmationui/aidl/vts/functional/VtsHalConfirmationUITargetTest.cpp
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ConfirmationIOAidlHalTest"
+
+#include <algorithm>
+#include <condition_variable>
+#include <future>
+#include <iostream>
+#include <memory>
+#include <mutex>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/confirmationui/BnConfirmationResultCallback.h>
+#include <aidl/android/hardware/confirmationui/IConfirmationUI.h>
+#include <aidl/android/hardware/confirmationui/TestModeCommands.h>
+#include <aidl/android/hardware/confirmationui/UIOption.h>
+#include <android-base/thread_annotations.h>
+#include <android/hardware/confirmationui/support/confirmationui_utils.h>
+#include <cutils/log.h>
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <cn-cbor/cn-cbor.h>
+
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <android/binder_status.h>
+
+static constexpr int TIMEOUT_PERIOD = 10;
+
+namespace aidl::android::hardware::confirmationui::test {
+using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType;
+using ::aidl::android::hardware::security::keymint::HardwareAuthToken;
+using ::android::hardware::confirmationui::support::auth_token_key_t;
+using ::android::hardware::confirmationui::support::ByteBufferProxy;
+using ::android::hardware::confirmationui::support::HMac;
+using ::android::hardware::confirmationui::support::hmac_t;
+using ::android::hardware::confirmationui::support::hton;
+using ::android::hardware::confirmationui::support::NullOr;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace {
+const auth_token_key_t testKey(static_cast<uint8_t>(IConfirmationUI::TEST_KEY_BYTE));
+
+class HMacImplementation {
+  public:
+    static NullOr<hmac_t> hmac256(const auth_token_key_t& key,
+                                  std::initializer_list<ByteBufferProxy> buffers) {
+        HMAC_CTX hmacCtx;
+        HMAC_CTX_init(&hmacCtx);
+        if (!HMAC_Init_ex(&hmacCtx, key.data(), key.size(), EVP_sha256(), nullptr)) {
+            return {};
+        }
+        for (auto& buffer : buffers) {
+            if (!HMAC_Update(&hmacCtx, buffer.data(), buffer.size())) {
+                return {};
+            }
+        }
+        hmac_t result;
+        if (!HMAC_Final(&hmacCtx, result.data(), nullptr)) {
+            return {};
+        }
+        return result;
+    }
+};
+
+using HMacer = HMac<HMacImplementation>;
+
+template <typename... Data>
+vector<uint8_t> testHMAC(const Data&... data) {
+    auto hmac = HMacer::hmac256(testKey, data...);
+    if (!hmac.isOk()) {
+        ADD_FAILURE() << "Failed to compute test hmac.  This is a self-test error.";
+        return {};
+    }
+    vector<uint8_t> result(hmac.value().size());
+    std::copy(hmac.value().data(), hmac.value().data() + hmac.value().size(), result.data());
+    return result;
+}
+
+template <typename T>
+auto toBytes(const T& v) -> const uint8_t (&)[sizeof(T)] {
+    return *reinterpret_cast<const uint8_t(*)[sizeof(T)]>(&v);
+}
+
+HardwareAuthToken makeTestToken(const TestModeCommands command, uint64_t timestamp = 0) {
+    HardwareAuthToken auth_token;
+    auth_token.challenge = static_cast<uint64_t>(command);
+    auth_token.userId = 0;
+    auth_token.authenticatorId = 0;
+    auth_token.authenticatorType = HardwareAuthenticatorType::NONE;
+    auth_token.timestamp = {static_cast<int64_t>(timestamp)};
+
+    // Canonical form  of auth-token v0
+    // version (1 byte)
+    // challenge (8 bytes)
+    // user_id (8 bytes)
+    // authenticator_id (8 bytes)
+    // authenticator_type (4 bytes)
+    // timestamp (8 bytes)
+    // total 37 bytes
+    auth_token.mac = testHMAC("\0",
+                              toBytes(auth_token.challenge),                //
+                              toBytes(auth_token.userId),                   //
+                              toBytes(auth_token.authenticatorId),          //
+                              toBytes(hton(auth_token.authenticatorType)),  //
+                              toBytes(hton(auth_token.timestamp)));         //
+
+    return auth_token;
+}
+
+#define DEBUG_CONFRIMATIONUI_UTILS_TEST
+
+#ifdef DEBUG_CONFRIMATIONUI_UTILS_TEST
+std::ostream& hexdump(std::ostream& out, const uint8_t* data, size_t size) {
+    for (size_t i = 0; i < size; ++i) {
+        uint8_t byte = data[i];
+        out << std::hex << std::setw(2) << std::setfill('0') << (unsigned)byte;
+        switch (i & 0xf) {
+            case 0xf:
+                out << "\n";
+                break;
+            case 7:
+                out << "  ";
+                break;
+            default:
+                out << " ";
+                break;
+        }
+    }
+    return out;
+}
+#endif
+
+constexpr char hex_value[256] = {0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 1,  2,  3,  4,  5,  6,  7, 8, 9, 0, 0, 0, 0, 0, 0,  // '0'..'9'
+                                 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 'A'..'F'
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 'a'..'f'
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+                                 0, 0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+std::string hex2str(std::string a) {
+    std::string b;
+    size_t num = a.size() / 2;
+    b.resize(num);
+    for (size_t i = 0; i < num; i++) {
+        b[i] = (hex_value[a[i * 2] & 0xFF] << 4) + (hex_value[a[i * 2 + 1] & 0xFF]);
+    }
+    return b;
+}
+
+int getReturnCode(const ::ndk::ScopedAStatus& result) {
+    if (result.isOk()) return IConfirmationUI::OK;
+
+    if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+        return static_cast<int>(result.getServiceSpecificError());
+    }
+    return result.getStatus();
+}
+
+}  // namespace
+
+class ConfirmationUIAidlTest : public ::testing::TestWithParam<std::string> {
+  public:
+    void TearDown() override { confirmator_->abort(); }
+    void SetUp() override {
+        std::string name = GetParam();
+        ASSERT_TRUE(AServiceManager_isDeclared(name.c_str())) << name;
+        ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str()));
+        ASSERT_NE(binder, nullptr);
+        confirmator_ = IConfirmationUI::fromBinder(binder);
+        ASSERT_NE(confirmator_, nullptr);
+    }
+
+    // Used as a mechanism to inform the test about data/event callback
+    inline void notify() {
+        std::unique_lock<std::mutex> lock(mtx_);
+        cv_.notify_one();
+    }
+
+    // Test code calls this function to wait for data/event callback
+    inline std::cv_status wait() {
+        std::unique_lock<std::mutex> lock(mtx_);
+        auto now = std::chrono::system_clock::now();
+        std::cv_status status = cv_.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
+        return status;
+    }
+
+  protected:
+    shared_ptr<IConfirmationUI> confirmator_;
+
+  private:
+    // synchronization objects
+    std::mutex mtx_;
+    std::condition_variable cv_;
+};
+
+class ConfirmationTestCallback
+    : public ::aidl::android::hardware::confirmationui::BnConfirmationResultCallback {
+  public:
+    ConfirmationTestCallback(ConfirmationUIAidlTest& parent) : parent_(parent){};
+    virtual ~ConfirmationTestCallback() = default;
+
+    ::ndk::ScopedAStatus result(int32_t err, const vector<uint8_t>& msg,
+                                const vector<uint8_t>& confToken) override {
+        error_ = err;
+        formattedMessage_ = msg;
+        confirmationToken_ = confToken;
+        parent_.notify();
+        return ndk::ScopedAStatus::ok();
+    }
+
+    bool verifyConfirmationToken() {
+        static constexpr char confirmationPrefix[] = "confirmation token";
+        EXPECT_EQ(32U, confirmationToken_.size());
+        return 32U == confirmationToken_.size() &&
+               !memcmp(confirmationToken_.data(),
+                       testHMAC(confirmationPrefix, formattedMessage_).data(), 32);
+    }
+
+    int error_;
+    vector<uint8_t> formattedMessage_;
+    vector<uint8_t> confirmationToken_;
+
+  private:
+    ConfirmationUIAidlTest& parent_;
+};
+
+struct CnCborDeleter {
+    void operator()(cn_cbor* ptr) { cn_cbor_free(ptr); }
+};
+
+typedef std::unique_ptr<cn_cbor, CnCborDeleter> CnCborPtr;
+
+// Simulates the User taping Ok
+TEST_P(ConfirmationUIAidlTest, UserOkTest) {
+    static constexpr char test_prompt[] = "Me first, gimme gimme!";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk());
+    // Simulate the user taping ok.
+    ASSERT_TRUE(confirmator_->deliverSecureInputEvent(makeTestToken(TestModeCommands::OK_EVENT))
+                        .isOk());
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::OK, conf_cb->error_);
+
+    ASSERT_TRUE(conf_cb->verifyConfirmationToken());
+
+    cn_cbor_errback cn_cbor_error;
+    auto parsed_message = CnCborPtr(cn_cbor_decode(
+            conf_cb->formattedMessage_.data(), conf_cb->formattedMessage_.size(), &cn_cbor_error));
+    // is parsable CBOR
+    ASSERT_TRUE(parsed_message.get());
+    // is a map
+    ASSERT_EQ(CN_CBOR_MAP, parsed_message->type);
+
+    // the message must have exactly 2 key value pairs.
+    // cn_cbor holds 2*<no_of_pairs> in the length field
+    ASSERT_EQ(4, parsed_message->length);
+    // map has key "prompt"
+    auto prompt = cn_cbor_mapget_string(parsed_message.get(), "prompt");
+    ASSERT_TRUE(prompt);
+    ASSERT_EQ(CN_CBOR_TEXT, prompt->type);
+    ASSERT_EQ(22, prompt->length);
+    ASSERT_EQ(0, memcmp(test_prompt, prompt->v.str, 22));
+    // map has key "extra"
+    auto extra_out = cn_cbor_mapget_string(parsed_message.get(), "extra");
+    ASSERT_TRUE(extra_out);
+    ASSERT_EQ(CN_CBOR_BYTES, extra_out->type);
+    ASSERT_EQ(3, extra_out->length);
+    ASSERT_EQ(0, memcmp(test_extra, extra_out->v.bytes, 3));
+}
+
+// Initiates a confirmation prompt with a message that is too long
+TEST_P(ConfirmationUIAidlTest, MessageTooLongTest) {
+    static constexpr uint8_t test_extra[IConfirmationUI::MAX_MESSAGE_SIZE] = {};
+    static constexpr char test_prompt[] = "D\'oh!";
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + sizeof(test_extra));
+    auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {});
+    ASSERT_EQ(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG, getReturnCode(result));
+}
+
+// If the message gets very long some HAL implementations might fail even before the message
+// reaches the trusted app implementation. But the HAL must still diagnose the correct error.
+TEST_P(ConfirmationUIAidlTest, MessageWayTooLongTest) {
+    static constexpr uint8_t test_extra[(IConfirmationUI::MAX_MESSAGE_SIZE)*10] = {};
+    static constexpr char test_prompt[] = "D\'oh!";
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + sizeof(test_extra));
+    auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {});
+    ASSERT_EQ(IConfirmationUI::UI_ERROR_MESSAGE_TOO_LONG, getReturnCode(result));
+}
+
+// Simulates the User tapping the Cancel
+TEST_P(ConfirmationUIAidlTest, UserCancelTest) {
+    static constexpr char test_prompt[] = "Me first, gimme gimme!";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk());
+
+    // Simulate the user taping ok.
+    ASSERT_TRUE(confirmator_->deliverSecureInputEvent(makeTestToken(TestModeCommands::CANCEL_EVENT))
+                        .isOk());
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::CANCELED, conf_cb->error_);
+
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Simulates the framework cancelling an ongoing prompt
+TEST_P(ConfirmationUIAidlTest, AbortTest) {
+    static constexpr char test_prompt[] = "Me first, gimme gimme!";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk());
+
+    confirmator_->abort();
+
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_);
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Tests if the confirmation dialog can successfully render 100 'W' characters as required by
+// the design guidelines.
+TEST_P(ConfirmationUIAidlTest, PortableMessageTest1) {
+    static constexpr char test_prompt[] =
+            "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
+            "WWWWWWWWWWWWWW";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk());
+
+    confirmator_->abort();
+
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_);
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Tests if the confirmation dialog can successfully render 100 'W' characters as required by
+// the design guidelines in magnified mode.
+TEST_P(ConfirmationUIAidlTest, PortableMessageTest1Magnified) {
+    static constexpr char test_prompt[] =
+            "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
+            "WWWWWWWWWWWWWW";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_
+                        ->promptUserConfirmation(conf_cb, prompt_text, extra, "en",
+                                                 {UIOption::ACCESSIBILITY_MAGNIFIED})
+                        .isOk());
+
+    confirmator_->abort();
+
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_);
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Tests if the confirmation dialog can successfully render 8 groups of 12 'W' characters as
+// required by the design guidelines.
+TEST_P(ConfirmationUIAidlTest, PortableMessageTest2) {
+    static constexpr char test_prompt[] =
+            "WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW "
+            "WWWWWWWWWWWW WWWWWWWWWWWW";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {}).isOk());
+
+    confirmator_->abort();
+
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_);
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Tests if the confirmation dialog can successfully render 8 groups of 12 'W' characters as
+// required by the design guidelines in magnified mode.
+TEST_P(ConfirmationUIAidlTest, PortableMessageTest2Magnified) {
+    static constexpr char test_prompt[] =
+            "WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW WWWWWWWWWWWW "
+            "WWWWWWWWWWWW WWWWWWWWWWWW";
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    ASSERT_TRUE(confirmator_
+                        ->promptUserConfirmation(conf_cb, prompt_text, extra, "en",
+                                                 {UIOption::ACCESSIBILITY_MAGNIFIED})
+                        .isOk());
+
+    confirmator_->abort();
+
+    // Wait for the callback.
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    ASSERT_EQ(IConfirmationUI::ABORTED, conf_cb->error_);
+    ASSERT_EQ(0U, conf_cb->confirmationToken_.size());
+    ASSERT_EQ(0U, conf_cb->formattedMessage_.size());
+}
+
+// Passing malformed UTF-8 to the confirmation UI
+// This test passes a string that ends in the middle of a multibyte character
+TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test1) {
+    static constexpr char test_prompt[] = {char(0xc0), 0};
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {});
+    ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result));
+}
+
+// Passing malformed UTF-8 to the confirmation UI
+// This test passes a string with a 5-byte character.
+TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test2) {
+    static constexpr char test_prompt[] = {char(0xf8), char(0x82), char(0x82),
+                                           char(0x82), char(0x82), 0};
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {});
+    ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result));
+}
+
+// Passing malformed UTF-8 to the confirmation UI
+// This test passes a string with a 2-byte character followed by a stray non UTF-8 character.
+TEST_P(ConfirmationUIAidlTest, MalformedUTF8Test3) {
+    static constexpr char test_prompt[] = {char(0xc0), char(0x82), char(0x83), 0};
+    static constexpr uint8_t test_extra[] = {0x1, 0x2, 0x3};
+    shared_ptr<ConfirmationTestCallback> conf_cb =
+            ::ndk::SharedRefBase::make<ConfirmationTestCallback>(*this);
+    vector<uint8_t> prompt_text(test_prompt, test_prompt + sizeof(test_prompt));
+    vector<uint8_t> extra(test_extra, test_extra + 3);
+    auto result = confirmator_->promptUserConfirmation(conf_cb, prompt_text, extra, "en", {});
+    ASSERT_EQ(IConfirmationUI::UI_ERROR_MALFORMED_UTF8ENCODING, getReturnCode(result));
+}
+
+// Test the implementation of HMAC SHA 256 against a golden blob.
+TEST(ConfirmationUITestSelfTest, HMAC256SelfTest) {
+    const char key_str[32] = "keykeykeykeykeykeykeykeykeykeyk";
+    const uint8_t(&key)[32] = *reinterpret_cast<const uint8_t(*)[32]>(key_str);
+    auto expected = hex2str("2377fbcaa7fb3f6c20cfa1d9ebc60e9922cf58c909e25e300f3cb57f7805c886");
+    auto result = HMacer::hmac256(key, "value1", "value2", "value3");
+
+#ifdef DEBUG_CONFRIMATIONUI_UTILS_TEST
+    hexdump(std::cout, reinterpret_cast<const uint8_t*>(expected.data()), 32) << std::endl;
+    hexdump(std::cout, result.value().data(), 32) << std::endl;
+#endif
+
+    ByteBufferProxy expected_bytes(expected);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(expected, result.value());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ConfirmationUIAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        PerInstance, ConfirmationUIAidlTest,
+        testing::ValuesIn(::android::getAidlHalInstanceNames(IConfirmationUI::descriptor)),
+        ::android::PrintInstanceNameToString);
+
+}  // namespace aidl::android::hardware::confirmationui::test
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/gatekeeper/OWNERS b/gatekeeper/OWNERS
new file mode 100644
index 0000000..d95b856
--- /dev/null
+++ b/gatekeeper/OWNERS
@@ -0,0 +1,2 @@
+swillden@google.com
+guangzhu@google.com
diff --git a/gatekeeper/aidl/Android.bp b/gatekeeper/aidl/Android.bp
new file mode 100644
index 0000000..6b1bc7e
--- /dev/null
+++ b/gatekeeper/aidl/Android.bp
@@ -0,0 +1,29 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+aidl_interface {
+    name: "android.hardware.gatekeeper",
+    vendor_available: true,
+    imports: [
+        "android.hardware.security.keymint-V2",
+    ],
+    srcs: ["android/hardware/gatekeeper/*.aidl"],
+    stability: "vintf",
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            apps_enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+    },
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
index 918ae3a..ae64ffc 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,11 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.gatekeeper;
+@VintfStability
+parcelable GatekeeperEnrollResponse {
+  int statusCode;
+  int timeoutMs;
+  long secureUserId;
+  byte[] data;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
index 918ae3a..f55da30 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,10 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.gatekeeper;
+@VintfStability
+parcelable GatekeeperVerifyResponse {
+  int statusCode;
+  int timeoutMs;
+  android.hardware.security.keymint.HardwareAuthToken hardwareAuthToken;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl
similarity index 71%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl
index 918ae3a..1a6f1ff 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/gatekeeper/aidl/aidl_api/android.hardware.gatekeeper/current/android/hardware/gatekeeper/IGatekeeper.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,16 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.gatekeeper;
+@SensitiveData @VintfStability
+interface IGatekeeper {
+  void deleteAllUsers();
+  void deleteUser(in int uid);
+  android.hardware.gatekeeper.GatekeeperEnrollResponse enroll(in int uid, in byte[] currentPasswordHandle, in byte[] currentPassword, in byte[] desiredPassword);
+  android.hardware.gatekeeper.GatekeeperVerifyResponse verify(in int uid, in long challenge, in byte[] enrolledPasswordHandle, in byte[] providedPassword);
+  const int STATUS_REENROLL = 1;
+  const int STATUS_OK = 0;
+  const int ERROR_GENERAL_FAILURE = -1;
+  const int ERROR_RETRY_TIMEOUT = -2;
+  const int ERROR_NOT_IMPLEMENTED = -3;
 }
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
new file mode 100644
index 0000000..04bacf0
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.gatekeeper;
+
+/**
+ * Gatekeeper response to enroll requests has this structure as mandatory part
+ */
+@VintfStability
+parcelable GatekeeperEnrollResponse {
+    /**
+     * Request completion status
+     */
+    int statusCode;
+    /**
+     * Retry timeout in ms, if code == ERROR_RETRY_TIMEOUT
+     * otherwise unused (0)
+     */
+    int timeoutMs;
+    /**
+     * secure user id.
+     */
+    long secureUserId;
+    /**
+     * optional crypto blob. Opaque to Android system.
+     */
+    byte[] data;
+}
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
new file mode 100644
index 0000000..bcf2d76
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.gatekeeper;
+
+import android.hardware.security.keymint.HardwareAuthToken;
+
+/**
+ * Gatekeeper response to verify requests has this structure as mandatory part
+ */
+@VintfStability
+parcelable GatekeeperVerifyResponse {
+    /**
+     * Request completion status
+     */
+    int statusCode;
+    /**
+     * Retry timeout in ms, if code == ERROR_RETRY_TIMEOUT
+     * otherwise unused (0)
+     */
+    int timeoutMs;
+    /**
+     * On successful verification of the password,
+     * IGatekeeper implementations must return hardware auth token
+     * in the response.
+     */
+    HardwareAuthToken hardwareAuthToken;
+}
diff --git a/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl b/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl
new file mode 100644
index 0000000..927293e
--- /dev/null
+++ b/gatekeeper/aidl/android/hardware/gatekeeper/IGatekeeper.aidl
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.gatekeeper;
+
+import android.hardware.gatekeeper.GatekeeperEnrollResponse;
+import android.hardware.gatekeeper.GatekeeperVerifyResponse;
+
+@VintfStability
+@SensitiveData
+interface IGatekeeper {
+    /**
+     * enroll and verify binder calls may return a ServiceSpecificException
+     * with the following error codes.
+     */
+    /* Success, but upper layers should re-enroll the verified password due to a version change. */
+    const int STATUS_REENROLL = 1;
+    /* operation is successful */
+    const int STATUS_OK = 0;
+    /* operation is successful. */
+    const int ERROR_GENERAL_FAILURE = -1;
+    /* operation should  be retried after timeout. */
+    const int ERROR_RETRY_TIMEOUT = -2;
+    /* operation is not implemented. */
+    const int ERROR_NOT_IMPLEMENTED = -3;
+
+    /**
+     * Deletes all the enrolled_password_handles for all uid's. Once called,
+     * no users must be enrolled on the device.
+     * This is an optional method.
+     *
+     * Service status return:
+     *
+     * OK if all the users are deleted successfully.
+     * ERROR_GENERAL_FAILURE on failure.
+     * ERROR_NOT_IMPLEMENTED if not implemented.
+     */
+    void deleteAllUsers();
+
+    /**
+     * Deletes the enrolledPasswordHandle associated with the uid. Once deleted
+     * the user cannot be verified anymore.
+     * This is an optional method.
+     *
+     * Service status return:
+     *
+     * OK if user is deleted successfully.
+     * ERROR_GENERAL_FAILURE on failure.
+     * ERROR_NOT_IMPLEMENTED if not implemented.
+     *
+     * @param uid The Android user identifier
+     */
+    void deleteUser(in int uid);
+
+    /**
+     * Enrolls desiredPassword, which may be derived from a user selected pin
+     * or password, with the private key used only for enrolling authentication
+     * factor data.
+     *
+     * If there was already a password enrolled, current password handle must be
+     * passed in currentPasswordHandle, and current password must be passed in
+     * currentPassword. Valid currentPassword must verify() against
+     * currentPasswordHandle.
+     *
+     * Service status return:
+     *
+     * OK if password is enrolled successfully.
+     * ERROR_GENERAL_FAILURE on failure.
+     * ERROR_NOT_IMPLEMENTED if not implemented.
+     *
+     * @param uid The Android user identifier
+     *
+     * @param currentPasswordHandle The currently enrolled password handle the user
+     *    wants to replace. May be empty only if there's no currently enrolled
+     *    password. Otherwise must be non-empty.
+     *
+     * @param currentPassword The user's current password in plain text.
+     *    it MUST verify against current_password_handle if the latter is not-empty
+     *
+     * @param desiredPassword The new password the user wishes to enroll in
+     *    plaintext.
+     *
+     * @return
+     *    On success, data buffer must contain the new password handle referencing
+     *    the password provided in desiredPassword.
+     *    This buffer can be used on subsequent calls to enroll or
+     *    verify. response.statusCode must contain either ERROR_RETRY_TIMEOUT or
+     *    STATUS_OK. On error, this buffer must be empty. This method may return
+     *    ERROR_GENERAL_FAILURE on failure.
+     *    If ERROR_RETRY_TIMEOUT is returned, response.timeout must be non-zero.
+     */
+    GatekeeperEnrollResponse enroll(in int uid, in byte[] currentPasswordHandle,
+            in byte[] currentPassword, in byte[] desiredPassword);
+
+    /**
+     * Verifies that providedPassword matches enrolledPasswordHandle.
+     *
+     * Implementations of this module may retain the result of this call
+     * to attest to the recency of authentication.
+     *
+     * On success, returns verification token in response.data, which shall be
+     * usable to attest password verification to other trusted services.
+     *
+     * Service status return:
+     *
+     * OK if password is enrolled successfully.
+     * ERROR_GENERAL_FAILURE on failure.
+     * ERROR_NOT_IMPLEMENTED if not implemented.
+     *
+     * @param uid The Android user identifier
+     *
+     * @param challenge An optional challenge to authenticate against, or 0.
+     *    Used when a separate authenticator requests password verification,
+     *    or for transactional password authentication.
+     *
+     * @param enrolledPasswordHandle The currently enrolled password handle that
+     *    user wishes to verify against. Must be non-empty.
+     *
+     * @param providedPassword The plaintext password to be verified against the
+     *    enrolledPasswordHandle
+     *
+     * @return
+     *    On success, a HardwareAuthToken resulting from this verification is returned.
+     *    response.statusCode must contain either ERROR_RETRY_TIMEOUT or
+     *    or STATUS_REENROLL or STATUS_OK.
+     *    On error, data buffer must be empty.
+     *    This method may return ERROR_GENERAL_FAILURE on failure.
+     *    If password re-enrollment is necessary, it must return STATUS_REENROLL.
+     *    If ERROR_RETRY_TIMEOUT is returned, response.timeout must be non-zero.
+     */
+    GatekeeperVerifyResponse verify(in int uid, in long challenge, in byte[] enrolledPasswordHandle,
+            in byte[] providedPassword);
+}
diff --git a/gatekeeper/aidl/vts/functional/Android.bp b/gatekeeper/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..2fa80de
--- /dev/null
+++ b/gatekeeper/aidl/vts/functional/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsHalGatekeeperTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+        "keymint_use_latest_hal_aidl_ndk_shared",
+    ],
+    srcs: ["VtsHalGatekeeperTargetTest.cpp"],
+    shared_libs: [
+        "libbinder_ndk",
+        "libbase",
+    ],
+    static_libs: ["android.hardware.gatekeeper-V1-ndk"],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp b/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp
new file mode 100644
index 0000000..c89243b
--- /dev/null
+++ b/gatekeeper/aidl/vts/functional/VtsHalGatekeeperTargetTest.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "gatekeeper_aidl_hal_test"
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/gatekeeper/GatekeeperEnrollResponse.h>
+#include <aidl/android/hardware/gatekeeper/GatekeeperVerifyResponse.h>
+#include <aidl/android/hardware/gatekeeper/IGatekeeper.h>
+#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
+#include <android-base/endian.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <hardware/hw_auth_token.h>
+
+#include <log/log.h>
+
+using aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse;
+using aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse;
+using aidl::android::hardware::gatekeeper::IGatekeeper;
+using aidl::android::hardware::security::keymint::HardwareAuthToken;
+using Status = ::ndk::ScopedAStatus;
+
+struct GatekeeperRequest {
+    uint32_t uid;
+    uint64_t challenge;
+    std::vector<uint8_t> curPwdHandle;
+    std::vector<uint8_t> curPwd;
+    std::vector<uint8_t> newPwd;
+    GatekeeperRequest() : uid(0), challenge(0) {}
+};
+
+// ASSERT_* macros generate return "void" internally
+// we have to use EXPECT_* if we return anything but "void"
+static void verifyAuthToken(GatekeeperVerifyResponse& rsp) {
+    uint32_t auth_type = static_cast<uint32_t>(rsp.hardwareAuthToken.authenticatorType);
+    uint64_t auth_tstamp = static_cast<uint64_t>(rsp.hardwareAuthToken.timestamp.milliSeconds);
+
+    EXPECT_EQ(HW_AUTH_PASSWORD, auth_type);
+    EXPECT_NE(UINT64_C(~0), auth_tstamp);
+    ALOGI("Authenticator ID: %016" PRIX64, rsp.hardwareAuthToken.authenticatorId);
+    EXPECT_NE(UINT32_C(0), rsp.hardwareAuthToken.userId);
+}
+
+// The main test class for Gatekeeper AIDL HAL.
+class GatekeeperAidlTest : public ::testing::TestWithParam<std::string> {
+  protected:
+    void setUid(uint32_t uid) { uid_ = uid; }
+
+    Status doEnroll(GatekeeperRequest& req, GatekeeperEnrollResponse& rsp) {
+        Status ret;
+        while (true) {
+            ret = gatekeeper_->enroll(uid_, req.curPwdHandle, req.curPwd, req.newPwd, &rsp);
+            if (ret.isOk()) break;
+            if (getReturnStatusCode(ret) != IGatekeeper::ERROR_RETRY_TIMEOUT) break;
+            ALOGI("%s: got retry code; retrying in 1 sec", __func__);
+            sleep(1);
+        }
+        return ret;
+    }
+
+    Status doVerify(GatekeeperRequest& req, GatekeeperVerifyResponse& rsp) {
+        Status ret;
+        while (true) {
+            ret = gatekeeper_->verify(uid_, req.challenge, req.curPwdHandle, req.newPwd, &rsp);
+            if (ret.isOk()) break;
+            if (getReturnStatusCode(ret) != IGatekeeper::ERROR_RETRY_TIMEOUT) break;
+            ALOGI("%s: got retry code; retrying in 1 sec", __func__);
+            sleep(1);
+        }
+        return ret;
+    }
+
+    Status doDeleteUser() { return gatekeeper_->deleteUser(uid_); }
+
+    Status doDeleteAllUsers() { return gatekeeper_->deleteAllUsers(); }
+
+    void generatePassword(std::vector<uint8_t>& password, uint8_t seed) {
+        password.resize(16);
+        memset(password.data(), seed, password.size());
+    }
+
+    void checkEnroll(GatekeeperEnrollResponse& rsp, Status& ret, bool expectSuccess) {
+        if (expectSuccess) {
+            EXPECT_TRUE(ret.isOk());
+            EXPECT_EQ(IGatekeeper::STATUS_OK, rsp.statusCode);
+            EXPECT_NE(nullptr, rsp.data.data());
+            EXPECT_GT(rsp.data.size(), UINT32_C(0));
+            EXPECT_NE(UINT32_C(0), rsp.secureUserId);
+        } else {
+            EXPECT_EQ(IGatekeeper::ERROR_GENERAL_FAILURE, getReturnStatusCode(ret));
+            EXPECT_EQ(UINT32_C(0), rsp.data.size());
+        }
+    }
+
+    void checkVerify(GatekeeperVerifyResponse& rsp, Status& ret, uint64_t challenge,
+                     bool expectSuccess) {
+        if (expectSuccess) {
+            EXPECT_TRUE(ret.isOk());
+            EXPECT_GE(rsp.statusCode, IGatekeeper::STATUS_OK);
+            EXPECT_LE(rsp.statusCode, IGatekeeper::STATUS_REENROLL);
+
+            verifyAuthToken(rsp);
+            EXPECT_EQ(challenge, rsp.hardwareAuthToken.challenge);
+        } else {
+            EXPECT_EQ(IGatekeeper::ERROR_GENERAL_FAILURE, getReturnStatusCode(ret));
+        }
+    }
+
+    void enrollNewPassword(std::vector<uint8_t>& password, GatekeeperEnrollResponse& rsp,
+                           bool expectSuccess) {
+        GatekeeperRequest req;
+        req.newPwd = password;
+        Status ret = doEnroll(req, rsp);
+        checkEnroll(rsp, ret, expectSuccess);
+    }
+
+    void verifyPassword(std::vector<uint8_t>& password, std::vector<uint8_t>& passwordHandle,
+                        uint64_t challenge, GatekeeperVerifyResponse& verifyRsp,
+                        bool expectSuccess) {
+        GatekeeperRequest verifyReq;
+
+        // build verify request for the same password (we want it to succeed)
+        verifyReq.newPwd = password;
+        // use enrolled password handle we've got
+        verifyReq.curPwdHandle = passwordHandle;
+        verifyReq.challenge = challenge;
+        Status ret = doVerify(verifyReq, verifyRsp);
+        checkVerify(verifyRsp, ret, challenge, expectSuccess);
+    }
+
+    int32_t getReturnStatusCode(const Status& result) {
+        if (!result.isOk()) {
+            if (result.getExceptionCode() == EX_SERVICE_SPECIFIC) {
+                return result.getServiceSpecificError();
+            }
+            return IGatekeeper::ERROR_GENERAL_FAILURE;
+        }
+        return IGatekeeper::STATUS_OK;
+    }
+
+  protected:
+    std::shared_ptr<IGatekeeper> gatekeeper_;
+    uint32_t uid_;
+
+  public:
+    GatekeeperAidlTest() : uid_(0) {}
+    virtual void SetUp() override {
+        gatekeeper_ = IGatekeeper::fromBinder(
+            ndk::SpAIBinder(AServiceManager_waitForService(GetParam().c_str())));
+        ASSERT_NE(nullptr, gatekeeper_.get());
+        doDeleteAllUsers();
+    }
+
+    virtual void TearDown() override { doDeleteAllUsers(); }
+};
+
+/**
+ * Ensure we can enroll new password
+ */
+TEST_P(GatekeeperAidlTest, EnrollSuccess) {
+    std::vector<uint8_t> password;
+    GatekeeperEnrollResponse rsp;
+    ALOGI("Testing Enroll (expected success)");
+    generatePassword(password, 0);
+    enrollNewPassword(password, rsp, true);
+    ALOGI("Testing Enroll done");
+}
+
+/**
+ * Ensure we can not enroll empty password
+ */
+TEST_P(GatekeeperAidlTest, EnrollNoPassword) {
+    std::vector<uint8_t> password;
+    GatekeeperEnrollResponse rsp;
+    ALOGI("Testing Enroll (expected failure)");
+    enrollNewPassword(password, rsp, false);
+    ALOGI("Testing Enroll done");
+}
+
+/**
+ * Ensure we can successfully verify previously enrolled password
+ */
+TEST_P(GatekeeperAidlTest, VerifySuccess) {
+    GatekeeperEnrollResponse enrollRsp;
+    GatekeeperVerifyResponse verifyRsp;
+    std::vector<uint8_t> password;
+
+    ALOGI("Testing Enroll+Verify (expected success)");
+    generatePassword(password, 0);
+    enrollNewPassword(password, enrollRsp, true);
+    verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);
+
+    ALOGI("Testing unenrolled password doesn't verify");
+    verifyRsp = {0, 0, {}};
+    generatePassword(password, 1);
+    verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);
+    ALOGI("Testing Enroll+Verify done");
+}
+
+/**
+ * Ensure we can securely update password (keep the same
+ * secure user_id) if we prove we know old password
+ */
+TEST_P(GatekeeperAidlTest, TrustedReenroll) {
+    GatekeeperEnrollResponse enrollRsp;
+    GatekeeperRequest reenrollReq;
+    GatekeeperEnrollResponse reenrollRsp;
+    GatekeeperVerifyResponse verifyRsp;
+    GatekeeperVerifyResponse reenrollVerifyRsp;
+    std::vector<uint8_t> password;
+    std::vector<uint8_t> newPassword;
+
+    generatePassword(password, 0);
+
+    ALOGI("Testing Trusted Reenroll (expected success)");
+    enrollNewPassword(password, enrollRsp, true);
+    verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+    ALOGI("Primary Enroll+Verify done");
+
+    generatePassword(newPassword, 1);
+    reenrollReq.newPwd = newPassword;
+    reenrollReq.curPwd = password;
+    reenrollReq.curPwdHandle = enrollRsp.data;
+
+    Status ret = doEnroll(reenrollReq, reenrollRsp);
+    checkEnroll(reenrollRsp, ret, true);
+    verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
+    ALOGI("Trusted ReEnroll+Verify done");
+
+    verifyAuthToken(verifyRsp);
+    verifyAuthToken(reenrollVerifyRsp);
+    EXPECT_EQ(verifyRsp.hardwareAuthToken.userId, reenrollVerifyRsp.hardwareAuthToken.userId);
+    ALOGI("Testing Trusted Reenroll done");
+}
+
+/**
+ * Ensure we can update password (and get new
+ * secure user_id) if we don't know old password
+ */
+TEST_P(GatekeeperAidlTest, UntrustedReenroll) {
+    GatekeeperEnrollResponse enrollRsp;
+    GatekeeperEnrollResponse reenrollRsp;
+    GatekeeperVerifyResponse verifyRsp;
+    GatekeeperVerifyResponse reenrollVerifyRsp;
+    std::vector<uint8_t> password;
+    std::vector<uint8_t> newPassword;
+
+    ALOGI("Testing Untrusted Reenroll (expected success)");
+    generatePassword(password, 0);
+    enrollNewPassword(password, enrollRsp, true);
+    verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+    ALOGI("Primary Enroll+Verify done");
+
+    generatePassword(newPassword, 1);
+    enrollNewPassword(newPassword, reenrollRsp, true);
+    verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
+    ALOGI("Untrusted ReEnroll+Verify done");
+
+    verifyAuthToken(verifyRsp);
+    verifyAuthToken(reenrollVerifyRsp);
+    EXPECT_NE(verifyRsp.hardwareAuthToken.userId, reenrollVerifyRsp.hardwareAuthToken.userId);
+    ALOGI("Testing Untrusted Reenroll done");
+}
+
+/**
+ * Ensure we don't get successful verify with invalid data
+ */
+TEST_P(GatekeeperAidlTest, VerifyNoData) {
+    std::vector<uint8_t> password;
+    std::vector<uint8_t> passwordHandle;
+    GatekeeperVerifyResponse verifyRsp;
+
+    ALOGI("Testing Verify (expected failure)");
+    verifyPassword(password, passwordHandle, 0, verifyRsp, false);
+    ALOGI("Testing Verify done");
+}
+
+/**
+ * Ensure we can not verify password after we enrolled it and then deleted user
+ */
+TEST_P(GatekeeperAidlTest, DeleteUserTest) {
+    std::vector<uint8_t> password;
+    GatekeeperEnrollResponse enrollRsp;
+    GatekeeperVerifyResponse verifyRsp;
+    ALOGI("Testing deleteUser (expected success)");
+    setUid(10001);
+    generatePassword(password, 0);
+    enrollNewPassword(password, enrollRsp, true);
+    verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+    ALOGI("Enroll+Verify done");
+    auto result = doDeleteUser();
+    EXPECT_TRUE(result.isOk() ||
+                (getReturnStatusCode(result) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+    ALOGI("DeleteUser done");
+    if (result.isOk()) {
+        verifyRsp = {0, 0, {}};
+        verifyPassword(password, enrollRsp.data, 0, verifyRsp, false);
+        ALOGI("Verify after Delete done (must fail)");
+    }
+    ALOGI("Testing deleteUser done: rsp=%" PRIi32, getReturnStatusCode(result));
+}
+
+/**
+ * Ensure we can not delete a user that does not exist
+ */
+TEST_P(GatekeeperAidlTest, DeleteInvalidUserTest) {
+    std::vector<uint8_t> password;
+    GatekeeperEnrollResponse enrollRsp;
+    GatekeeperVerifyResponse verifyRsp;
+    ALOGI("Testing deleteUser (expected failure)");
+    setUid(10002);
+    generatePassword(password, 0);
+    enrollNewPassword(password, enrollRsp, true);
+    verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
+    ALOGI("Enroll+Verify done");
+
+    // Delete the user
+    Status result1 = doDeleteUser();
+    EXPECT_TRUE(result1.isOk() ||
+                (getReturnStatusCode(result1) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+
+    // Delete the user again
+    Status result2 = doDeleteUser();
+    int32_t retCode2 = getReturnStatusCode(result2);
+    EXPECT_TRUE((retCode2 == IGatekeeper::ERROR_NOT_IMPLEMENTED) ||
+                (retCode2 == IGatekeeper::ERROR_GENERAL_FAILURE));
+    ALOGI("DeleteUser done");
+    ALOGI("Testing deleteUser done: rsp=%" PRIi32, retCode2);
+}
+
+/**
+ * Ensure we can not verify passwords after we enrolled them and then deleted
+ * all users
+ */
+TEST_P(GatekeeperAidlTest, DeleteAllUsersTest) {
+    struct UserData {
+        uint32_t userId;
+        std::vector<uint8_t> password;
+        GatekeeperEnrollResponse enrollRsp;
+        GatekeeperVerifyResponse verifyRsp;
+        UserData(int id) { userId = id; }
+    } users[3]{10001, 10002, 10003};
+    ALOGI("Testing deleteAllUsers (expected success)");
+
+    // enroll multiple users
+    for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+        setUid(users[i].userId);
+        generatePassword(users[i].password, (i % 255) + 1);
+        enrollNewPassword(users[i].password, users[i].enrollRsp, true);
+    }
+    ALOGI("Multiple users enrolled");
+
+    // verify multiple users
+    for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+        setUid(users[i].userId);
+        verifyPassword(users[i].password, users[i].enrollRsp.data, 0, users[i].verifyRsp, true);
+    }
+    ALOGI("Multiple users verified");
+
+    Status result = doDeleteAllUsers();
+    EXPECT_TRUE(result.isOk() ||
+                (getReturnStatusCode(result) == IGatekeeper::ERROR_NOT_IMPLEMENTED));
+    ALOGI("All users deleted");
+
+    if (result.isOk()) {
+        // verify multiple users after they are deleted; all must fail
+        for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
+            setUid(users[i].userId);
+            users[i].verifyRsp = {0, 0, {}};
+            verifyPassword(users[i].password, users[i].enrollRsp.data, 0, users[i].verifyRsp,
+                           false);
+        }
+        ALOGI("Multiple users verified after delete (all must fail)");
+    }
+
+    ALOGI("Testing deleteAllUsers done: rsp=%" PRIi32, getReturnStatusCode(result));
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GatekeeperAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+    PerInstance, GatekeeperAidlTest,
+    testing::ValuesIn(android::getAidlHalInstanceNames(IGatekeeper::descriptor)),
+    android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h b/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
index 1d81f7b..775ae9f 100644
--- a/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
+++ b/graphics/composer/aidl/include/android/hardware/graphics/composer3/ComposerClientWriter.h
@@ -59,13 +59,13 @@
 
 namespace aidl::android::hardware::graphics::composer3 {
 
-class ComposerClientWriter {
+class ComposerClientWriter final {
   public:
     static constexpr std::optional<ClockMonotonicTimestamp> kNoTimestamp = std::nullopt;
 
     ComposerClientWriter() { reset(); }
 
-    virtual ~ComposerClientWriter() { reset(); }
+    ~ComposerClientWriter() { reset(); }
 
     void reset() {
         mDisplayCommand.reset();
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 31ab400..a57875a 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -10,6 +10,10 @@
 cc_library_static {
     name: "android.hardware.identity-libeic-hal-common",
     vendor_available: true,
+    defaults: [
+        "identity_use_latest_hal_aidl_ndk_static",
+        "keymint_use_latest_hal_aidl_ndk_static",
+    ],
     srcs: [
         "common/IdentityCredential.cpp",
         "common/IdentityCredentialStore.cpp",
@@ -40,9 +44,7 @@
         "libsoft_attestation_cert",
         "libpuresoftkeymasterdevice",
         "android.hardware.identity-support-lib",
-        "android.hardware.identity-V4-ndk",
         "android.hardware.keymaster-V3-ndk",
-        "android.hardware.security.keymint-V2-ndk",
     ],
 }
 
@@ -83,6 +85,7 @@
     vintf_fragments: ["identity-default.xml"],
     vendor: true,
     defaults: [
+        "identity_use_latest_hal_aidl_ndk_static",
         "keymint_use_latest_hal_aidl_ndk_static",
     ],
     cflags: [
@@ -106,7 +109,6 @@
         "libsoft_attestation_cert",
         "libpuresoftkeymasterdevice",
         "android.hardware.identity-support-lib",
-        "android.hardware.identity-V4-ndk",
         "android.hardware.keymaster-V3-ndk",
         "android.hardware.identity-libeic-hal-common",
         "android.hardware.identity-libeic-library",
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index 51ab110..dd29819 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -11,6 +11,7 @@
     name: "VtsHalIdentityTargetTest",
     defaults: [
         "VtsHalTargetTestDefaults",
+        "identity_use_latest_hal_aidl_cpp_static",
         "keymint_use_latest_hal_aidl_cpp_static",
         "keymint_use_latest_hal_aidl_ndk_static",
         "use_libaidlvintf_gtest_helper_static",
@@ -46,7 +47,6 @@
         "libpuresoftkeymasterdevice",
         "android.hardware.keymaster@4.0",
         "android.hardware.identity-support-lib",
-        "android.hardware.identity-V4-cpp",
         "android.hardware.keymaster-V3-cpp",
         "android.hardware.keymaster-V3-ndk",
         "libkeymaster4support",
diff --git a/input/common/aidl/Android.bp b/input/common/aidl/Android.bp
index 390d8b5..95a14b2 100644
--- a/input/common/aidl/Android.bp
+++ b/input/common/aidl/Android.bp
@@ -9,6 +9,7 @@
 
 aidl_interface {
     name: "android.hardware.input.common",
+    host_supported: true,
     vendor_available: true,
     srcs: ["android/hardware/input/common/*.aidl"],
     stability: "vintf",
diff --git a/input/processor/aidl/Android.bp b/input/processor/aidl/Android.bp
index 354816e..773bb49 100644
--- a/input/processor/aidl/Android.bp
+++ b/input/processor/aidl/Android.bp
@@ -9,6 +9,7 @@
 
 aidl_interface {
     name: "android.hardware.input.processor",
+    host_supported: true,
     vendor_available: true,
     srcs: ["android/hardware/input/processor/*.aidl"],
     imports: [
diff --git a/power/OWNERS b/power/OWNERS
new file mode 100644
index 0000000..7229b22
--- /dev/null
+++ b/power/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 826709
+
+# ADPF virtual team
+lpy@google.com
+wvw@google.com
diff --git a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl
index 1d3ecb7..928668c 100644
--- a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl
+++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/IPowerHintSession.aidl
@@ -39,4 +39,5 @@
   oneway void pause();
   oneway void resume();
   oneway void close();
+  oneway void sendHint(android.hardware.power.SessionHint hint);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SessionHint.aidl
similarity index 88%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SessionHint.aidl
index 918ae3a..b19c77b 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SessionHint.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -31,11 +31,11 @@
 // with such a backward incompatible change, it has a high risk of breaking
 // later when a module using the interface is updated, e.g., Mainline modules.
 
-package android.hardware.radio.ims;
-@Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+package android.hardware.power;
+@Backing(type="int") @VintfStability
+enum SessionHint {
+  CPU_LOAD_UP = 0,
+  CPU_LOAD_DOWN = 1,
+  CPU_LOAD_RESET = 2,
+  CPU_LOAD_RESUME = 3,
 }
diff --git a/power/aidl/android/hardware/power/IPowerHintSession.aidl b/power/aidl/android/hardware/power/IPowerHintSession.aidl
index c289448..4ca9c54 100644
--- a/power/aidl/android/hardware/power/IPowerHintSession.aidl
+++ b/power/aidl/android/hardware/power/IPowerHintSession.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.power;
 
+import android.hardware.power.SessionHint;
 import android.hardware.power.WorkDuration;
 
 @VintfStability
@@ -56,4 +57,12 @@
      * Close the session to release resources.
      */
     void close();
+
+    /**
+     * Gives information to the PowerHintSession about upcoming or unexpected
+     * changes in load to supplement the normal updateTarget/reportActual cycle.
+     *
+     * @param hint The hint to provide to the PowerHintSession
+     */
+    void sendHint(SessionHint hint);
 }
diff --git a/power/aidl/android/hardware/power/SessionHint.aidl b/power/aidl/android/hardware/power/SessionHint.aidl
new file mode 100644
index 0000000..6a95035
--- /dev/null
+++ b/power/aidl/android/hardware/power/SessionHint.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.power;
+
+@VintfStability
+@Backing(type="int")
+enum SessionHint {
+    /**
+     * This hint indicates an increase in CPU workload intensity. It means that
+     * this hint session needs extra CPU resources to meet the target duration.
+     * This hint must be sent before reporting the actual duration to the session.
+     */
+    CPU_LOAD_UP = 0,
+    /**
+     * This hint indicates a decrease in CPU workload intensity. It means that
+     * this hint session can reduce CPU resources and still meet the target duration.
+     */
+    CPU_LOAD_DOWN = 1,
+    /*
+     * This hint indicates an upcoming CPU workload that is completely changed and
+     * unknown. It means that the hint session should reset CPU resources to a known
+     * baseline to prepare for an arbitrary load, and must wake up if inactive.
+     */
+    CPU_LOAD_RESET = 2,
+    /*
+     * This hint indicates that the most recent CPU workload is resuming after a
+     * period of inactivity. It means that the hint session should allocate similar
+     * CPU resources to what was used previously, and must wake up if inactive.
+     */
+    CPU_LOAD_RESUME = 3,
+
+}
diff --git a/power/aidl/default/Android.bp b/power/aidl/default/Android.bp
index 223b9d5..d1257f3 100644
--- a/power/aidl/default/Android.bp
+++ b/power/aidl/default/Android.bp
@@ -30,7 +30,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
-        "android.hardware.power-V3-ndk",
+        "android.hardware.power-V4-ndk",
     ],
     srcs: [
         "main.cpp",
diff --git a/power/aidl/default/power-default.xml b/power/aidl/default/power-default.xml
index 927ba22..f5dd6b9 100644
--- a/power/aidl/default/power-default.xml
+++ b/power/aidl/default/power-default.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.power</name>
-        <version>3</version>
+        <version>4</version>
         <fqname>IPower/default</fqname>
     </hal>
 </manifest>
diff --git a/power/aidl/vts/Android.bp b/power/aidl/vts/Android.bp
index ea398ac..d5cc2b6 100644
--- a/power/aidl/vts/Android.bp
+++ b/power/aidl/vts/Android.bp
@@ -32,7 +32,7 @@
         "libbinder_ndk",
     ],
     static_libs: [
-        "android.hardware.power-V3-ndk",
+        "android.hardware.power-V4-ndk",
     ],
     test_suites: [
         "vts",
diff --git a/power/aidl/vts/VtsHalPowerTargetTest.cpp b/power/aidl/vts/VtsHalPowerTargetTest.cpp
index 2cfa04a..dcf075f 100644
--- a/power/aidl/vts/VtsHalPowerTargetTest.cpp
+++ b/power/aidl/vts/VtsHalPowerTargetTest.cpp
@@ -34,12 +34,16 @@
 using android::hardware::power::IPower;
 using android::hardware::power::IPowerHintSession;
 using android::hardware::power::Mode;
+using android::hardware::power::SessionHint;
 using android::hardware::power::WorkDuration;
 
 const std::vector<Boost> kBoosts{ndk::enum_range<Boost>().begin(), ndk::enum_range<Boost>().end()};
 
 const std::vector<Mode> kModes{ndk::enum_range<Mode>().begin(), ndk::enum_range<Mode>().end()};
 
+const std::vector<SessionHint> kSessionHints{ndk::enum_range<SessionHint>().begin(),
+                                             ndk::enum_range<SessionHint>().end()};
+
 const std::vector<Boost> kInvalidBoosts = {
         static_cast<Boost>(static_cast<int32_t>(kBoosts.front()) - 1),
         static_cast<Boost>(static_cast<int32_t>(kBoosts.back()) + 1),
@@ -50,6 +54,11 @@
         static_cast<Mode>(static_cast<int32_t>(kModes.back()) + 1),
 };
 
+const std::vector<SessionHint> kInvalidSessionHints = {
+        static_cast<SessionHint>(static_cast<int32_t>(kSessionHints.front()) - 1),
+        static_cast<SessionHint>(static_cast<int32_t>(kSessionHints.back()) + 1),
+};
+
 class DurationWrapper : public WorkDuration {
   public:
     DurationWrapper(int64_t dur, int64_t time) {
@@ -175,6 +184,7 @@
     ASSERT_TRUE(session->close().isOk());
     session.reset();
 }
+
 TEST_P(PowerAidl, createHintSessionFailed) {
     std::shared_ptr<IPowerHintSession> session;
     auto status = power->createHintSession(getpid(), getuid(), kEmptyTids, 16666666L, &session);
@@ -198,6 +208,21 @@
     ASSERT_TRUE(session->reportActualWorkDuration(kDurations).isOk());
 }
 
+TEST_P(PowerAidl, sendSessionHint) {
+    std::shared_ptr<IPowerHintSession> session;
+    auto status = power->createHintSession(getpid(), getuid(), kSelfTids, 16666666L, &session);
+    if (!status.isOk()) {
+        EXPECT_TRUE(isUnknownOrUnsupported(status));
+        return;
+    }
+    for (const auto& sessionHint : kSessionHints) {
+        ASSERT_TRUE(session->sendHint(sessionHint).isOk());
+    }
+    for (const auto& sessionHint : kInvalidSessionHints) {
+        ASSERT_TRUE(session->sendHint(sessionHint).isOk());
+    }
+}
+
 // FIXED_PERFORMANCE mode is required for all devices which ship on Android 11
 // or later
 TEST_P(PowerAidl, hasFixedPerformance) {
diff --git a/radio/aidl/Android.bp b/radio/aidl/Android.bp
index b655dd2..bac4b6d 100644
--- a/radio/aidl/Android.bp
+++ b/radio/aidl/Android.bp
@@ -36,7 +36,7 @@
     host_supported: true,
     srcs: ["android/hardware/radio/config/*.aidl"],
     stability: "vintf",
-    imports: ["android.hardware.radio"],
+    imports: ["android.hardware.radio-V1"],
     backend: {
         cpp: {
             enabled: true,
@@ -60,7 +60,7 @@
     host_supported: true,
     srcs: ["android/hardware/radio/data/*.aidl"],
     stability: "vintf",
-    imports: ["android.hardware.radio"],
+    imports: ["android.hardware.radio-V1"],
     backend: {
         cpp: {
             enabled: true,
@@ -84,7 +84,7 @@
     host_supported: true,
     srcs: ["android/hardware/radio/messaging/*.aidl"],
     stability: "vintf",
-    imports: ["android.hardware.radio"],
+    imports: ["android.hardware.radio-V1"],
     backend: {
         cpp: {
             enabled: true,
@@ -108,7 +108,7 @@
     host_supported: true,
     srcs: ["android/hardware/radio/modem/*.aidl"],
     stability: "vintf",
-    imports: ["android.hardware.radio"],
+    imports: ["android.hardware.radio-V1"],
     backend: {
         cpp: {
             enabled: true,
@@ -158,7 +158,7 @@
     srcs: ["android/hardware/radio/sim/*.aidl"],
     stability: "vintf",
     imports: [
-        "android.hardware.radio",
+        "android.hardware.radio-V1",
         "android.hardware.radio.config",
     ],
     backend: {
@@ -187,7 +187,7 @@
     host_supported: true,
     srcs: ["android/hardware/radio/voice/*.aidl"],
     stability: "vintf",
-    imports: ["android.hardware.radio"],
+    imports: ["android.hardware.radio-V1"],
     backend: {
         cpp: {
             enabled: true,
diff --git a/radio/aidl/OWNERS b/radio/aidl/OWNERS
new file mode 100644
index 0000000..7b01aad
--- /dev/null
+++ b/radio/aidl/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 20868
+include ../1.0/vts/OWNERS
+
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/AmrMode.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/AmrMode.aidl
index dad6d0a..5179169 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/AmrMode.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/AmrMode.aidl
@@ -34,13 +34,13 @@
 package android.hardware.radio.ims.media;
 @Backing(type="int") @VintfStability
 enum AmrMode {
-  AMR_MODE_0 = 0,
-  AMR_MODE_1 = 1,
-  AMR_MODE_2 = 2,
-  AMR_MODE_3 = 3,
-  AMR_MODE_4 = 4,
-  AMR_MODE_5 = 5,
-  AMR_MODE_6 = 6,
-  AMR_MODE_7 = 7,
-  AMR_MODE_8 = 8,
+  AMR_MODE_0 = 1 << 0,
+  AMR_MODE_1 = 1 << 1,
+  AMR_MODE_2 = 1 << 2,
+  AMR_MODE_3 = 1 << 3,
+  AMR_MODE_4 = 1 << 4,
+  AMR_MODE_5 = 1 << 5,
+  AMR_MODE_6 = 1 << 6,
+  AMR_MODE_7 = 1 << 7,
+  AMR_MODE_8 = 1 << 8,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/EvsMode.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/EvsMode.aidl
index 6b56d5d..a067357 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/EvsMode.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims.media/current/android/hardware/radio/ims/media/EvsMode.aidl
@@ -34,25 +34,25 @@
 package android.hardware.radio.ims.media;
 @Backing(type="int") @VintfStability
 enum EvsMode {
-  EVS_MODE_0 = 0,
-  EVS_MODE_1 = 1,
-  EVS_MODE_2 = 2,
-  EVS_MODE_3 = 3,
-  EVS_MODE_4 = 4,
-  EVS_MODE_5 = 5,
-  EVS_MODE_6 = 6,
-  EVS_MODE_7 = 7,
-  EVS_MODE_8 = 8,
-  EVS_MODE_9 = 9,
-  EVS_MODE_10 = 10,
-  EVS_MODE_11 = 11,
-  EVS_MODE_12 = 12,
-  EVS_MODE_13 = 13,
-  EVS_MODE_14 = 14,
-  EVS_MODE_15 = 15,
-  EVS_MODE_16 = 16,
-  EVS_MODE_17 = 17,
-  EVS_MODE_18 = 18,
-  EVS_MODE_19 = 19,
-  EVS_MODE_20 = 20,
+  EVS_MODE_0 = 1 << 0,
+  EVS_MODE_1 = 1 << 1,
+  EVS_MODE_2 = 1 << 2,
+  EVS_MODE_3 = 1 << 3,
+  EVS_MODE_4 = 1 << 4,
+  EVS_MODE_5 = 1 << 5,
+  EVS_MODE_6 = 1 << 6,
+  EVS_MODE_7 = 1 << 7,
+  EVS_MODE_8 = 1 << 8,
+  EVS_MODE_9 = 1 << 9,
+  EVS_MODE_10 = 1 << 10,
+  EVS_MODE_11 = 1 << 11,
+  EVS_MODE_12 = 1 << 12,
+  EVS_MODE_13 = 1 << 13,
+  EVS_MODE_14 = 1 << 14,
+  EVS_MODE_15 = 1 << 15,
+  EVS_MODE_16 = 1 << 16,
+  EVS_MODE_17 = 1 << 17,
+  EVS_MODE_18 = 1 << 18,
+  EVS_MODE_19 = 1 << 19,
+  EVS_MODE_20 = 1 << 20,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/EpsFallbackReason.aidl
similarity index 94%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/EpsFallbackReason.aidl
index 918ae3a..ebea903 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/EpsFallbackReason.aidl
@@ -33,9 +33,7 @@
 
 package android.hardware.radio.ims;
 @Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+enum EpsFallbackReason {
+  NO_NETWORK_TRIGGER = 1,
+  NO_NETWORK_RESPONSE = 2,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioIms.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioIms.aidl
index f774555..ee5c572 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioIms.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioIms.aidl
@@ -38,6 +38,7 @@
   oneway void updateImsRegistrationInfo(int serial, in android.hardware.radio.ims.ImsRegistration imsRegistration);
   oneway void startImsTraffic(int serial, in String token, android.hardware.radio.ims.ImsTrafficType imsTrafficType, android.hardware.radio.AccessNetwork accessNetworkType);
   oneway void stopImsTraffic(int serial, in String token);
+  oneway void triggerEpsFallback(int serial, in android.hardware.radio.ims.EpsFallbackReason reason);
   oneway void setResponseFunctions(in android.hardware.radio.ims.IRadioImsResponse radioImsResponse, in android.hardware.radio.ims.IRadioImsIndication radioImsIndication);
   oneway void sendAnbrQuery(int serial, android.hardware.radio.ims.ImsStreamType mediaType, android.hardware.radio.ims.ImsStreamDirection direction, int bitsPerSecond);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsIndication.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsIndication.aidl
index f6d5bc4..63f29f3 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsIndication.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsIndication.aidl
@@ -36,5 +36,5 @@
 interface IRadioImsIndication {
   oneway void onConnectionSetupFailure(in android.hardware.radio.RadioIndicationType type, in String token, in android.hardware.radio.ims.ConnectionFailureInfo info);
   oneway void notifyAnbr(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.ims.ImsStreamType mediaType, in android.hardware.radio.ims.ImsStreamDirection direction, int bitsPerSecond);
-  oneway void triggerImsDeregistration(in android.hardware.radio.RadioIndicationType type);
+  oneway void triggerImsDeregistration(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.ims.ImsDeregistrationReason reason);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsResponse.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsResponse.aidl
index 6f6b446..d4fd23f 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsResponse.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/IRadioImsResponse.aidl
@@ -38,5 +38,6 @@
   oneway void updateImsRegistrationInfoResponse(in android.hardware.radio.RadioResponseInfo info);
   oneway void startImsTrafficResponse(in android.hardware.radio.RadioResponseInfo info, in @nullable android.hardware.radio.ims.ConnectionFailureInfo failureInfo);
   oneway void stopImsTrafficResponse(in android.hardware.radio.RadioResponseInfo info);
+  oneway void triggerEpsFallbackResponse(in android.hardware.radio.RadioResponseInfo info);
   oneway void sendAnbrQueryResponse(in android.hardware.radio.RadioResponseInfo info);
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsDeregistrationReason.aidl
similarity index 93%
copy from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
copy to radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsDeregistrationReason.aidl
index 918ae3a..330374e 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsDeregistrationReason.aidl
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2022 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"),
+ * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -33,9 +33,8 @@
 
 package android.hardware.radio.ims;
 @Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
-  NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+enum ImsDeregistrationReason {
+  REASON_SIM_REMOVED = 1,
+  REASON_SIM_REFRESH = 2,
+  REASON_NETWORK_MODE_CHANGED = 3,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsRegistration.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsRegistration.aidl
index 84ae2cb..1c4c12a 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsRegistration.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsRegistration.aidl
@@ -36,7 +36,7 @@
 parcelable ImsRegistration {
   android.hardware.radio.ims.ImsRegistrationState regState;
   android.hardware.radio.AccessNetwork accessNetworkType;
-  android.hardware.radio.ims.ImsFailureReason reason;
+  android.hardware.radio.ims.SuggestedAction suggestedAction;
   int capabilities;
   const int IMS_MMTEL_CAPABILITY_NONE = 0;
   const int IMS_MMTEL_CAPABILITY_VOICE = 1;
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SrvccCall.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SrvccCall.aidl
index a09cca6..a8b7cfc 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SrvccCall.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SrvccCall.aidl
@@ -54,9 +54,6 @@
   enum CallSubState {
     NONE = 0,
     PREALERTING = 1,
-    ANSWERED = 2,
-    HOLD_REQUESTED = 3,
-    RESUME_REQUESTED = 4,
   }
   @Backing(type="int") @VintfStability
   enum ToneType {
diff --git a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SuggestedAction.aidl
similarity index 95%
rename from radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
rename to radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SuggestedAction.aidl
index 918ae3a..da19774 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.ims/current/android/hardware/radio/ims/SuggestedAction.aidl
@@ -33,9 +33,8 @@
 
 package android.hardware.radio.ims;
 @Backing(type="int") @JavaDerive(toString=true) @VintfStability
-enum ImsFailureReason {
+enum SuggestedAction {
   NONE = 0,
-  FATAL_ERROR = 1,
-  REPEATED_ERROR = 2,
-  TEMPORARY_ERROR = 3,
+  TRIGGER_PLMN_BLOCK = 1,
+  TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
index 3248b43..28d8862 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
@@ -37,10 +37,6 @@
   boolean noinit;
   android.hardware.radio.network.Cdma2000RegistrationInfo cdmaInfo;
   android.hardware.radio.network.EutranRegistrationInfo eutranInfo;
-  /**
-   * @deprecated Use ngranInfo instead.
-   */
   android.hardware.radio.network.NrVopsInfo ngranNrVopsInfo;
   boolean geranDtmSupported;
-  @nullable android.hardware.radio.network.NrRegistrationInfo ngranInfo;
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl
index cb598f3..af17b43 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EmergencyRegResult.aidl
@@ -40,4 +40,6 @@
   boolean isEmcBearerSupported;
   byte nwProvidedEmc;
   byte nwProvidedEmf;
+  String mcc = "";
+  String mnc = "";
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EutranRegistrationInfo.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EutranRegistrationInfo.aidl
index 9c8f692..93df3a4 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EutranRegistrationInfo.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/EutranRegistrationInfo.aidl
@@ -36,14 +36,13 @@
 parcelable EutranRegistrationInfo {
   android.hardware.radio.network.LteVopsInfo lteVopsInfo;
   android.hardware.radio.network.NrIndicators nrIndicators;
-  android.hardware.radio.network.EutranRegistrationInfo.AttachType lteAttachType;
+  android.hardware.radio.network.EutranRegistrationInfo.AttachResultType lteAttachResultType;
   int extraInfo;
   const int EXTRA_CSFB_NOT_PREFERRED = 1;
   const int EXTRA_SMS_ONLY = 2;
-  enum AttachType {
+  enum AttachResultType {
     NONE = 0,
     EPS_ONLY = 1,
     COMBINED = 2,
-    EMERGENCY = 3,
   }
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/RegState.aidl b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/RegState.aidl
index e6e7999..711c9ac 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/RegState.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.network/current/android/hardware/radio/network/RegState.aidl
@@ -44,4 +44,5 @@
   NOT_REG_MT_SEARCHING_OP_EM = 12,
   REG_DENIED_EM = 13,
   UNKNOWN_EM = 14,
+  REG_EM = 20,
 }
diff --git a/radio/aidl/android/hardware/radio/ims/EpsFallbackReason.aidl b/radio/aidl/android/hardware/radio/ims/EpsFallbackReason.aidl
new file mode 100644
index 0000000..670638b
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/ims/EpsFallbackReason.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"),
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.ims;
+
+@VintfStability
+@JavaDerive(toString=true)
+@Backing(type="int")
+enum EpsFallbackReason {
+    /**
+     * If VoNR is not supported and EPS fallback is not triggered by network then UE initiated EPS
+     * fallback would be triggered by IMS stack with this reason. The modem shall locally release
+     * the 5G NR SA RRC connection and acquire the LTE network and perform a tracking area update
+     * procedure. After the EPS fallback procedure is completed, the call setup for voice will
+     * be established.
+     */
+    NO_NETWORK_TRIGGER = 1,
+
+    /**
+     * If the UE doesn't receive any response for SIP INVITE within a certain time in 5G NR SA,
+     * UE initiated EPS fallback will be triggered with this reason. The modem shall reset its data
+     * buffer of IMS PDUs to prevent the ghost call. After the EPS fallback procedure is completed,
+     * the VoLTE call will be established.
+     */
+    NO_NETWORK_RESPONSE = 2,
+}
diff --git a/radio/aidl/android/hardware/radio/ims/IRadioIms.aidl b/radio/aidl/android/hardware/radio/ims/IRadioIms.aidl
index 26f0337..e9796f4 100644
--- a/radio/aidl/android/hardware/radio/ims/IRadioIms.aidl
+++ b/radio/aidl/android/hardware/radio/ims/IRadioIms.aidl
@@ -17,6 +17,7 @@
 package android.hardware.radio.ims;
 
 import android.hardware.radio.AccessNetwork;
+import android.hardware.radio.ims.EpsFallbackReason;
 import android.hardware.radio.ims.ImsRegistration;
 import android.hardware.radio.ims.ImsStreamDirection;
 import android.hardware.radio.ims.ImsTrafficType;
@@ -103,6 +104,17 @@
     void stopImsTraffic(int serial, in String token);
 
     /**
+     * Triggers the UE initiated EPS fallback when a MO voice call failed to establish on 5G NR
+     * network and network didn't initiate a fallback.
+     *
+     * @param serial Serial number of request
+     * @param reason Specifies the reason that causes EPS fallback
+     *
+     * Response function is IRadioImsResponse.triggerEpsFallbackResponse()
+     */
+    void triggerEpsFallback(int serial, in EpsFallbackReason reason);
+
+    /**
      * Set response functions for IMS radio requests and indications.
      *
      * @param radioImsResponse Object containing response functions
diff --git a/radio/aidl/android/hardware/radio/ims/IRadioImsIndication.aidl b/radio/aidl/android/hardware/radio/ims/IRadioImsIndication.aidl
index 7a8cc0e..ea286c4 100644
--- a/radio/aidl/android/hardware/radio/ims/IRadioImsIndication.aidl
+++ b/radio/aidl/android/hardware/radio/ims/IRadioImsIndication.aidl
@@ -18,6 +18,7 @@
 
 import android.hardware.radio.RadioIndicationType;
 import android.hardware.radio.ims.ConnectionFailureInfo;
+import android.hardware.radio.ims.ImsDeregistrationReason;
 import android.hardware.radio.ims.ImsStreamDirection;
 import android.hardware.radio.ims.ImsStreamType;
 
@@ -60,6 +61,7 @@
      * the network detach procedure.
      *
      * @param type Type of radio indication
+     * @param reason the reason why the deregistration is triggered
      */
-     void triggerImsDeregistration(in RadioIndicationType type);
+     void triggerImsDeregistration(in RadioIndicationType type, in ImsDeregistrationReason reason);
 }
diff --git a/radio/aidl/android/hardware/radio/ims/IRadioImsResponse.aidl b/radio/aidl/android/hardware/radio/ims/IRadioImsResponse.aidl
index f15b422..351e748 100644
--- a/radio/aidl/android/hardware/radio/ims/IRadioImsResponse.aidl
+++ b/radio/aidl/android/hardware/radio/ims/IRadioImsResponse.aidl
@@ -110,5 +110,22 @@
      *   RadioError:REQUEST_NOT_SUPPORTED
      *   RadioError:NO_RESOURCES
      */
+    void triggerEpsFallbackResponse(in RadioResponseInfo info);
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     *
+     * Valid errors returned:
+     *   RadioError:NONE
+     *   RadioError:RADIO_NOT_AVAILABLE
+     *   RadioError:INVALID_STATE
+     *   RadioError:NO_MEMORY
+     *   RadioError:SYSTEM_ERR
+     *   RadioError:MODEM_ERR
+     *   RadioError:INTERNAL_ERR
+     *   RadioError:INVALID_ARGUMENTS
+     *   RadioError:REQUEST_NOT_SUPPORTED
+     *   RadioError:NO_RESOURCES
+     */
     void sendAnbrQueryResponse(in RadioResponseInfo info);
 }
diff --git a/radio/aidl/android/hardware/radio/ims/ImsDeregistrationReason.aidl b/radio/aidl/android/hardware/radio/ims/ImsDeregistrationReason.aidl
new file mode 100644
index 0000000..83c0321
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/ims/ImsDeregistrationReason.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.ims;
+
+@VintfStability
+@JavaDerive(toString=true)
+@Backing(type="int")
+enum ImsDeregistrationReason {
+    /**
+     * Radio shall send this reason to IMS stack to perform graceful de-registration
+     * due to SIM card is removed.
+     */
+    REASON_SIM_REMOVED = 1,
+    /**
+     * Radio shall send this reason to IMS stack to perform graceful de-registration
+     * due to SIM refresh that needs a NAS detach and re-attach.
+     */
+    REASON_SIM_REFRESH = 2,
+    /**
+     * Radio shall send this reason to IMS stack to perform graceful de-registration
+     * due to allowed network types bitmask changed that results in NAS detach.
+     */
+    REASON_NETWORK_MODE_CHANGED = 3,
+}
diff --git a/radio/aidl/android/hardware/radio/ims/ImsRegistration.aidl b/radio/aidl/android/hardware/radio/ims/ImsRegistration.aidl
index b727af0..662f9e9 100644
--- a/radio/aidl/android/hardware/radio/ims/ImsRegistration.aidl
+++ b/radio/aidl/android/hardware/radio/ims/ImsRegistration.aidl
@@ -17,8 +17,8 @@
 package android.hardware.radio.ims;
 
 import android.hardware.radio.AccessNetwork;
-import android.hardware.radio.ims.ImsFailureReason;
 import android.hardware.radio.ims.ImsRegistrationState;
+import android.hardware.radio.ims.SuggestedAction;
 
 @VintfStability
 @JavaDerive(toString=true)
@@ -42,8 +42,8 @@
      */
     AccessNetwork accessNetworkType;
 
-    /** Indicates a failure reason for IMS registration. */
-    ImsFailureReason reason;
+    /** Indicates the expected action for the radio to do. */
+    SuggestedAction suggestedAction;
 
     /**
      * Values are bitwise ORs of IMS_MMTEL_CAPABILITY_* constants and IMS_RCS_CAPABILITIES.
diff --git a/radio/aidl/android/hardware/radio/ims/SrvccCall.aidl b/radio/aidl/android/hardware/radio/ims/SrvccCall.aidl
index f6a751e..38e6cdb 100644
--- a/radio/aidl/android/hardware/radio/ims/SrvccCall.aidl
+++ b/radio/aidl/android/hardware/radio/ims/SrvccCall.aidl
@@ -33,9 +33,6 @@
         NONE,
         /** Pre-alerting state. Applicable for MT calls only */
         PREALERTING,
-        ANSWERED,
-        HOLD_REQUESTED,
-        RESUME_REQUESTED,
     }
 
     @VintfStability
diff --git a/radio/aidl/android/hardware/radio/ims/ImsFailureReason.aidl b/radio/aidl/android/hardware/radio/ims/SuggestedAction.aidl
similarity index 82%
rename from radio/aidl/android/hardware/radio/ims/ImsFailureReason.aidl
rename to radio/aidl/android/hardware/radio/ims/SuggestedAction.aidl
index 21da582..a17b8a4 100644
--- a/radio/aidl/android/hardware/radio/ims/ImsFailureReason.aidl
+++ b/radio/aidl/android/hardware/radio/ims/SuggestedAction.aidl
@@ -19,26 +19,19 @@
 @VintfStability
 @JavaDerive(toString=true)
 @Backing(type="int")
-enum ImsFailureReason {
+enum SuggestedAction {
     /** Default value */
-    NONE,
-
+    NONE = 0,
     /**
      * Indicates that the IMS registration is failed with fatal error such as 403 or 404
      * on all P-CSCF addresses. The radio shall block the current PLMN or disable
      * the RAT as per the carrier requirements.
      */
-    FATAL_ERROR,
-
+    TRIGGER_PLMN_BLOCK = 1 << 0,
     /**
      * Indicates that the IMS registration on current PLMN failed multiple times.
-     * The radio shall block the current PLMN or disable the RAT as per the
-     * carrier requirements.
+     * The radio shall block the current PLMN or disable the RAT during EPS or 5GS mobility
+     * management timer value as per the carrier requirements.
      */
-    REPEATED_ERROR,
-
-    /**
-     * Indicates that IMS registration has failed temporarily. A retry will be done shortly.
-     */
-    TEMPORARY_ERROR,
+    TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 1 << 1,
 }
diff --git a/radio/aidl/android/hardware/radio/ims/media/AmrMode.aidl b/radio/aidl/android/hardware/radio/ims/media/AmrMode.aidl
index e9065b3..66d8ef0 100644
--- a/radio/aidl/android/hardware/radio/ims/media/AmrMode.aidl
+++ b/radio/aidl/android/hardware/radio/ims/media/AmrMode.aidl
@@ -21,21 +21,21 @@
 @Backing(type="int")
 enum AmrMode {
     /** 4.75 kbps for AMR / 6.6 kbps for AMR-WB */
-    AMR_MODE_0 = 0,
+    AMR_MODE_0 = 1 << 0,
     /** 5.15 kbps for AMR / 8.855 kbps for AMR-WB */
-    AMR_MODE_1 = 1,
+    AMR_MODE_1 = 1 << 1,
     /** 5.9 kbps for AMR / 12.65 kbps for AMR-WB */
-    AMR_MODE_2 = 2,
+    AMR_MODE_2 = 1 << 2,
     /** 6.7 kbps for AMR / 14.25 kbps for AMR-WB */
-    AMR_MODE_3 = 3,
+    AMR_MODE_3 = 1 << 3,
     /** 7.4 kbps for AMR / 15.85 kbps for AMR-WB */
-    AMR_MODE_4 = 4,
+    AMR_MODE_4 = 1 << 4,
     /** 7.95 kbps for AMR / 18.25 kbps for AMR-WB */
-    AMR_MODE_5 = 5,
+    AMR_MODE_5 = 1 << 5,
     /** 10.2 kbps for AMR / 19.85 kbps for AMR-WB */
-    AMR_MODE_6 = 6,
+    AMR_MODE_6 = 1 << 6,
     /** 12.2 kbps for AMR / 23.05 kbps for AMR-WB */
-    AMR_MODE_7 = 7,
+    AMR_MODE_7 = 1 << 7,
     /** Silence frame for AMR / 23.85 kbps for AMR-WB */
-    AMR_MODE_8 = 8,
+    AMR_MODE_8 = 1 << 8,
 }
diff --git a/radio/aidl/android/hardware/radio/ims/media/EvsMode.aidl b/radio/aidl/android/hardware/radio/ims/media/EvsMode.aidl
index fabec5e..95bd6c7 100644
--- a/radio/aidl/android/hardware/radio/ims/media/EvsMode.aidl
+++ b/radio/aidl/android/hardware/radio/ims/media/EvsMode.aidl
@@ -21,45 +21,45 @@
 @Backing(type="int")
 enum EvsMode {
     /** 6.6 kbps for EVS AMR-WB IO */
-    EVS_MODE_0 = 0,
+    EVS_MODE_0 = 1 << 0,
     /** 8.855 kbps for AMR-WB IO */
-    EVS_MODE_1 = 1,
+    EVS_MODE_1 = 1 << 1,
     /** 12.65 kbps for AMR-WB IO */
-    EVS_MODE_2 = 2,
+    EVS_MODE_2 = 1 << 2,
     /** 14.25 kbps for AMR-WB IO */
-    EVS_MODE_3 = 3,
+    EVS_MODE_3 = 1 << 3,
     /** 15.85 kbps for AMR-WB IO */
-    EVS_MODE_4 = 4,
+    EVS_MODE_4 = 1 << 4,
     /** 18.25 kbps for AMR-WB IO */
-    EVS_MODE_5 = 5,
+    EVS_MODE_5 = 1 << 5,
     /** 19.85 kbps for AMR-WB IO */
-    EVS_MODE_6 = 6,
+    EVS_MODE_6 = 1 << 6,
     /** 23.05 kbps for AMR-WB IO */
-    EVS_MODE_7 = 7,
+    EVS_MODE_7 = 1 << 7,
     /** 23.85 kbps for AMR-WB IO */
-    EVS_MODE_8 = 8,
+    EVS_MODE_8 = 1 << 8,
     /** 5.9 kbps for EVS primary */
-    EVS_MODE_9 = 9,
+    EVS_MODE_9 = 1 << 9,
     /** 7.2 kbps for EVS primary */
-    EVS_MODE_10 = 10,
+    EVS_MODE_10 = 1 << 10,
     /** 8.0 kbps for EVS primary */
-    EVS_MODE_11 = 11,
+    EVS_MODE_11 = 1 << 11,
     /** 9.6 kbps for EVS primary */
-    EVS_MODE_12 = 12,
+    EVS_MODE_12 = 1 << 12,
     /** 13.2 kbps for EVS primary */
-    EVS_MODE_13 = 13,
+    EVS_MODE_13 = 1 << 13,
     /** 16.4 kbps for EVS primary */
-    EVS_MODE_14 = 14,
+    EVS_MODE_14 = 1 << 14,
     /** 24.4 kbps for EVS primary */
-    EVS_MODE_15 = 15,
+    EVS_MODE_15 = 1 << 15,
     /** 32.0 kbps for EVS primary */
-    EVS_MODE_16 = 16,
+    EVS_MODE_16 = 1 << 16,
     /** 48.0 kbps for EVS primary */
-    EVS_MODE_17 = 17,
+    EVS_MODE_17 = 1 << 17,
     /** 64.0 kbps for EVS primary */
-    EVS_MODE_18 = 18,
+    EVS_MODE_18 = 1 << 18,
     /** 96.0 kbps for EVS primary */
-    EVS_MODE_19 = 19,
+    EVS_MODE_19 = 1 << 19,
     /** 128.0 kbps for EVS primary */
-    EVS_MODE_20 = 20,
+    EVS_MODE_20 = 1 << 20,
 }
diff --git a/radio/aidl/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl b/radio/aidl/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
index bc56e9c..8b95ced 100644
--- a/radio/aidl/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
+++ b/radio/aidl/android/hardware/radio/network/AccessTechnologySpecificInfo.aidl
@@ -18,7 +18,6 @@
 
 import android.hardware.radio.network.Cdma2000RegistrationInfo;
 import android.hardware.radio.network.EutranRegistrationInfo;
-import android.hardware.radio.network.NrRegistrationInfo;
 import android.hardware.radio.network.NrVopsInfo;
 
 @VintfStability
@@ -31,18 +30,10 @@
      * Network capabilities for voice over PS services. This info is valid only on NR network and
      * must be present when the device is camped on NR. NrVopsInfo must be empty when the device is
      * not camped on NR.
-     * @deprecated Use ngranInfo instead.
      */
     NrVopsInfo ngranNrVopsInfo;
     /**
      * True if the dual transfer mode is supported. Refer to 3GPP TS 44.108 section 3.4.25.3
      */
     boolean geranDtmSupported;
-
-    /**
-     * Network registration information and network capabilities for voice over PS services.
-     * This info is valid only on NR network and must be present when the device is camped on NR.
-     * NrRegistrationInfo must be empty when the device is not camped on NR.
-     */
-    @nullable NrRegistrationInfo ngranInfo;
 }
diff --git a/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl b/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl
index cf5caa4..ec70e17 100644
--- a/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl
+++ b/radio/aidl/android/hardware/radio/network/EmergencyRegResult.aidl
@@ -16,8 +16,8 @@
 
 package android.hardware.radio.network;
 import android.hardware.radio.AccessNetwork;
-import android.hardware.radio.network.RegState;
 import android.hardware.radio.network.Domain;
+import android.hardware.radio.network.RegState;
 
 @VintfStability
 @JavaDerive(toString=true)
@@ -54,4 +54,10 @@
      * This should not be set if UE is not in 5G mode.
      */
     byte nwProvidedEmf;
+
+    /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+    String mcc = "";
+
+    /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+    String mnc = "";
 }
diff --git a/radio/aidl/android/hardware/radio/network/EutranRegistrationInfo.aidl b/radio/aidl/android/hardware/radio/network/EutranRegistrationInfo.aidl
index 3b40050..b986944 100644
--- a/radio/aidl/android/hardware/radio/network/EutranRegistrationInfo.aidl
+++ b/radio/aidl/android/hardware/radio/network/EutranRegistrationInfo.aidl
@@ -22,15 +22,13 @@
 @VintfStability
 @JavaDerive(toString=true)
 parcelable EutranRegistrationInfo {
-    enum AttachType {
+    enum AttachResultType {
         /** Default value. */
         NONE,
         /** LTE is attached with eps only. */
         EPS_ONLY,
         /** LTE combined EPS and IMSI attach. */
         COMBINED,
-        /** LTE is attached for emergency bearer service. */
-        EMERGENCY,
     }
 
     /** LTE combined attach with CSFB not preferred */
@@ -55,7 +53,7 @@
      * The type of network attachment. This info is valid only on LTE network and must be present
      * when device has attached to the network.
      */
-    AttachType lteAttachType;
+    AttachResultType lteAttachResultType;
 
     /** Values are bitwise ORs of EXTRA_* constants */
     int extraInfo;
diff --git a/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl b/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
index d98a31b..243e949 100644
--- a/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
+++ b/radio/aidl/android/hardware/radio/network/IRadioNetworkResponse.aidl
@@ -580,7 +580,6 @@
      *
      * Valid errors returned:
      *   RadioError:NONE
-     *   RadioError:REQUEST_NOT_SUPPORTED
      *   RadioError:RADIO_NOT_AVAILABLE
      *   RadioError:MODEM_ERR
      *   RadioError:INVALID_ARGUMENTS
@@ -592,7 +591,6 @@
      *
      * Valid errors returned:
      *   RadioError:NONE
-     *   RadioError:REQUEST_NOT_SUPPORTED
      *   RadioError:RADIO_NOT_AVAILABLE
      *   RadioError:MODEM_ERR
      *   RadioError:INVALID_ARGUMENTS
@@ -604,7 +602,6 @@
      *
      * Valid errors returned:
      *   RadioError:NONE
-     *   RadioError:REQUEST_NOT_SUPPORTED
      *   RadioError:RADIO_NOT_AVAILABLE
      *   RadioError:MODEM_ERR
      */
@@ -615,7 +612,6 @@
      *
      * Valid errors returned:
      *   RadioError:NONE
-     *   RadioError:REQUEST_NOT_SUPPORTED
      *   RadioError:RADIO_NOT_AVAILABLE
      *   RadioError:MODEM_ERR
      */
diff --git a/radio/aidl/android/hardware/radio/network/NrRegistrationInfo.aidl b/radio/aidl/android/hardware/radio/network/NrRegistrationInfo.aidl
deleted file mode 100644
index cbf233a..0000000
--- a/radio/aidl/android/hardware/radio/network/NrRegistrationInfo.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.radio.network;
-
-import android.hardware.radio.network.EutranRegistrationInfo;
-import android.hardware.radio.network.NrVopsInfo;
-
-@VintfStability
-parcelable NrRegistrationInfo {
-    /** SMS over NAS is allowed - TS 24.501 9.11.3.6. */
-    const int EXTRA_SMS_OVER_NAS_ALLOWED = 1 << 0;
-
-    /** Registered for emergency services - TS 24.501 9.11.3.6. */
-    const int EXTRA_REGISTERED_FOR_EMERGENCY = 1 << 1;
-
-    /** Network capabilities for voice over PS services. */
-    NrVopsInfo ngranNrVopsInfo;
-
-    /** Values are bitwise ORs of EXTRA_* constants */
-    int extraInfo;
-}
diff --git a/radio/aidl/android/hardware/radio/network/RegState.aidl b/radio/aidl/android/hardware/radio/network/RegState.aidl
index 3f13783..2827d19 100644
--- a/radio/aidl/android/hardware/radio/network/RegState.aidl
+++ b/radio/aidl/android/hardware/radio/network/RegState.aidl
@@ -65,4 +65,10 @@
      * Same as UNKNOWN but indicates that emergency calls are enabled
      */
     UNKNOWN_EM = 14,
+    /**
+     * Emergency attached in LTE or emergency registered in 5GS.
+     * Reference: 3GPP TS 24.301 9.9.3.11 EPS attach type.
+     * Reference: 3GPP TS 24.501 9.11.3.6 5GS registration result.
+     */
+    REG_EM = 20,
 }
diff --git a/radio/aidl/compat/libradiocompat/Android.bp b/radio/aidl/compat/libradiocompat/Android.bp
index 487d91b..f79e045 100644
--- a/radio/aidl/compat/libradiocompat/Android.bp
+++ b/radio/aidl/compat/libradiocompat/Android.bp
@@ -39,7 +39,7 @@
         "android.hardware.radio.data-V1-ndk",
         "android.hardware.radio.messaging-V1-ndk",
         "android.hardware.radio.modem-V1-ndk",
-        "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.network-V2-ndk",
         "android.hardware.radio.sim-V1-ndk",
         "android.hardware.radio.voice-V1-ndk",
         "android.hardware.radio@1.0",
diff --git a/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioNetwork.h b/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioNetwork.h
index 9784665..c714190 100644
--- a/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioNetwork.h
+++ b/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioNetwork.h
@@ -90,6 +90,16 @@
             ::aidl::android::hardware::radio::network::UsageSetting usageSetting) override;
     ::ndk::ScopedAStatus getUsageSetting(int32_t serial) override;
 
+    ::ndk::ScopedAStatus setEmergencyMode(
+            int32_t serial,
+            const ::aidl::android::hardware::radio::network::EmergencyMode emergencyMode) override;
+    ::ndk::ScopedAStatus triggerEmergencyNetworkScan(
+            int32_t serial,
+            const ::aidl::android::hardware::radio::network::EmergencyNetworkScanTrigger&
+                    scanTrigger) override;
+    ::ndk::ScopedAStatus cancelEmergencyNetworkScan(int32_t serial) override;
+    ::ndk::ScopedAStatus exitEmergencyMode(int32_t serial) override;
+
   protected:
     std::shared_ptr<::aidl::android::hardware::radio::network::IRadioNetworkResponse> respond();
 
diff --git a/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp b/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
index d5e2a8d..ff30ac3 100644
--- a/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
+++ b/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
@@ -311,4 +311,33 @@
     return ok();
 }
 
+ScopedAStatus RadioNetwork::setEmergencyMode(int32_t serial, aidl::EmergencyMode) {
+    LOG_CALL << serial;
+    LOG(ERROR) << " setEmergencyMode is unsupported by HIDL HALs";
+    respond()->setEmergencyModeResponse(notSupported(serial), {});
+    return ok();
+}
+
+ScopedAStatus RadioNetwork::triggerEmergencyNetworkScan(int32_t serial,
+        const aidl::EmergencyNetworkScanTrigger&) {
+    LOG_CALL << serial;
+    LOG(ERROR) << " triggerEmergencyNetworkScan is unsupported by HIDL HALs";
+    respond()->triggerEmergencyNetworkScanResponse(notSupported(serial));
+    return ok();
+}
+
+ScopedAStatus RadioNetwork::cancelEmergencyNetworkScan(int32_t serial) {
+    LOG_CALL << serial;
+    LOG(ERROR) << " cancelEmergencyNetworkScan is unsupported by HIDL HALs";
+    respond()->cancelEmergencyNetworkScanResponse(notSupported(serial));
+    return ok();
+}
+
+ScopedAStatus RadioNetwork::exitEmergencyMode(int32_t serial) {
+    LOG_CALL << serial;
+    LOG(ERROR) << " exitEmergencyMode is unsupported by HIDL HALs";
+    respond()->exitEmergencyModeResponse(notSupported(serial));
+    return ok();
+}
+
 }  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/service/Android.bp b/radio/aidl/compat/service/Android.bp
index 52eb71f..4dbaef4 100644
--- a/radio/aidl/compat/service/Android.bp
+++ b/radio/aidl/compat/service/Android.bp
@@ -42,7 +42,7 @@
         "android.hardware.radio.data-V1-ndk",
         "android.hardware.radio.messaging-V1-ndk",
         "android.hardware.radio.modem-V1-ndk",
-        "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.network-V2-ndk",
         "android.hardware.radio.sim-V1-ndk",
         "android.hardware.radio.voice-V1-ndk",
         "android.hardware.radio@1.0",
diff --git a/radio/aidl/vts/OWNERS b/radio/aidl/vts/OWNERS
deleted file mode 100644
index e75c6c8..0000000
--- a/radio/aidl/vts/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 20868
-include ../../1.0/vts/OWNERS
-
diff --git a/radio/aidl/vts/radio_ims_indication.cpp b/radio/aidl/vts/radio_ims_indication.cpp
index 0637813..8e98d85 100644
--- a/radio/aidl/vts/radio_ims_indication.cpp
+++ b/radio/aidl/vts/radio_ims_indication.cpp
@@ -28,6 +28,7 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus RadioImsIndication::triggerImsDeregistration(RadioIndicationType /*type*/) {
+ndk::ScopedAStatus RadioImsIndication::triggerImsDeregistration(RadioIndicationType /*type*/,
+        ImsDeregistrationReason /*reason*/) {
     return ndk::ScopedAStatus::ok();
 }
\ No newline at end of file
diff --git a/radio/aidl/vts/radio_ims_response.cpp b/radio/aidl/vts/radio_ims_response.cpp
index 7a90ebd..62912ea 100644
--- a/radio/aidl/vts/radio_ims_response.cpp
+++ b/radio/aidl/vts/radio_ims_response.cpp
@@ -45,6 +45,12 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus RadioImsResponse::triggerEpsFallbackResponse(const RadioResponseInfo& info) {
+    rspInfo = info;
+    parent_ims.notify(info.serial);
+    return ndk::ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus RadioImsResponse::sendAnbrQueryResponse(const RadioResponseInfo& info) {
     rspInfo = info;
     parent_ims.notify(info.serial);
diff --git a/radio/aidl/vts/radio_ims_test.cpp b/radio/aidl/vts/radio_ims_test.cpp
index d3bdd4b..27cc8d9 100644
--- a/radio/aidl/vts/radio_ims_test.cpp
+++ b/radio/aidl/vts/radio_ims_test.cpp
@@ -94,7 +94,7 @@
     ImsRegistration regInfo;
     regInfo.regState = ImsRegistrationState::NOT_REGISTERED;
     regInfo.accessNetworkType = AccessNetwork::EUTRAN;
-    regInfo.reason = ImsFailureReason::NONE;
+    regInfo.suggestedAction = SuggestedAction::NONE;
     regInfo.capabilities = ImsRegistration::IMS_MMTEL_CAPABILITY_NONE;
 
     ndk::ScopedAStatus res =
@@ -164,6 +164,32 @@
 }
 
 /*
+ * Test IRadioIms.triggerEpsFallback() for the response returned.
+ */
+TEST_P(RadioImsTest, triggerEpsFallback) {
+    if (!deviceSupportsFeature(FEATURE_TELEPHONY_IMS)) {
+        ALOGI("Skipping triggerEpsFallback because ims is not supported in device");
+        return;
+    } else {
+        ALOGI("Running triggerEpsFallback because ims is supported in device");
+    }
+
+    serial = GetRandomSerialNumber();
+
+    ndk::ScopedAStatus res =
+            radio_ims->triggerEpsFallback(serial, EpsFallbackReason::NO_NETWORK_TRIGGER);
+    ASSERT_OK(res);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_ims->rspInfo.type);
+    EXPECT_EQ(serial, radioRsp_ims->rspInfo.serial);
+
+    ALOGI("triggerEpsFallback, rspInfo.error = %s\n",
+              toString(radioRsp_ims->rspInfo.error).c_str());
+
+    verifyError(radioRsp_ims->rspInfo.error);
+}
+
+/*
  * Test IRadioIms.sendAnbrQuery() for the response returned.
  */
 TEST_P(RadioImsTest, sendAnbrQuery) {
diff --git a/radio/aidl/vts/radio_ims_utils.h b/radio/aidl/vts/radio_ims_utils.h
index c9ecf4a..98fd3cb 100644
--- a/radio/aidl/vts/radio_ims_utils.h
+++ b/radio/aidl/vts/radio_ims_utils.h
@@ -48,6 +48,8 @@
 
     virtual ndk::ScopedAStatus stopImsTrafficResponse(const RadioResponseInfo& info) override;
 
+    virtual ndk::ScopedAStatus triggerEpsFallbackResponse(const RadioResponseInfo& info) override;
+
     virtual ndk::ScopedAStatus sendAnbrQueryResponse(const RadioResponseInfo& info) override;
 };
 
@@ -66,7 +68,8 @@
     virtual ndk::ScopedAStatus notifyAnbr(RadioIndicationType type, ImsStreamType mediaType,
             ImsStreamDirection direction, int bitsPerSecond) override;
 
-    virtual ndk::ScopedAStatus triggerImsDeregistration(RadioIndicationType type) override;
+    virtual ndk::ScopedAStatus triggerImsDeregistration(RadioIndicationType type,
+            ImsDeregistrationReason reason) override;
 };
 
 // The main test class for Radio AIDL Ims.
diff --git a/radio/aidl/vts/radio_network_indication.cpp b/radio/aidl/vts/radio_network_indication.cpp
index 73c9ec8..ae3bd4b 100644
--- a/radio/aidl/vts/radio_network_indication.cpp
+++ b/radio/aidl/vts/radio_network_indication.cpp
@@ -93,7 +93,7 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus RadioNetworkIndication::emergencyNetworkScanResult(RadioIndicationType /*type*/,
-                                                     const EmergencyRegResult& /*result*/) {
+ndk::ScopedAStatus RadioNetworkIndication::emergencyNetworkScanResult(
+        RadioIndicationType /*type*/, const EmergencyRegResult& /*result*/) {
     return ndk::ScopedAStatus::ok();
 }
diff --git a/radio/aidl/vts/radio_network_response.cpp b/radio/aidl/vts/radio_network_response.cpp
index 1baf31c..5599c03 100644
--- a/radio/aidl/vts/radio_network_response.cpp
+++ b/radio/aidl/vts/radio_network_response.cpp
@@ -267,13 +267,15 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus RadioNetworkResponse::setEmergencyModeResponse(const RadioResponseInfo& info, const EmergencyRegResult& /*regState*/) {
+ndk::ScopedAStatus RadioNetworkResponse::setEmergencyModeResponse(
+        const RadioResponseInfo& info, const EmergencyRegResult& /*regState*/) {
     rspInfo = info;
     parent_network.notify(info.serial);
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus RadioNetworkResponse::triggerEmergencyNetworkScanResponse(const RadioResponseInfo& info) {
+ndk::ScopedAStatus RadioNetworkResponse::triggerEmergencyNetworkScanResponse(
+        const RadioResponseInfo& info) {
     rspInfo = info;
     parent_network.notify(info.serial);
     return ndk::ScopedAStatus::ok();
@@ -285,7 +287,8 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus RadioNetworkResponse::cancelEmergencyNetworkScanResponse(const RadioResponseInfo& info) {
+ndk::ScopedAStatus RadioNetworkResponse::cancelEmergencyNetworkScanResponse(
+        const RadioResponseInfo& info) {
     rspInfo = info;
     parent_network.notify(info.serial);
     return ndk::ScopedAStatus::ok();
diff --git a/radio/aidl/vts/radio_network_test.cpp b/radio/aidl/vts/radio_network_test.cpp
index 119977b..ccde32b 100644
--- a/radio/aidl/vts/radio_network_test.cpp
+++ b/radio/aidl/vts/radio_network_test.cpp
@@ -1838,3 +1838,81 @@
     }
     LOG(DEBUG) << "supplyNetworkDepersonalization finished";
 }
+
+/*
+ * Test IRadioNetwork.setEmergencyMode() for the response returned.
+ */
+TEST_P(RadioNetworkTest, setEmergencyMode) {
+    LOG(DEBUG) << "setEmergencyMode";
+    serial = GetRandomSerialNumber();
+
+    radio_network->setEmergencyMode(serial, EmergencyMode::EMERGENCY_WWAN);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_network->rspInfo.type);
+    EXPECT_EQ(serial, radioRsp_network->rspInfo.serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(
+            radioRsp_network->rspInfo.error,
+            {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE,
+             RadioError::MODEM_ERR, RadioError::INVALID_ARGUMENTS}));
+    LOG(DEBUG) << "setEmergencyMode finished";
+}
+
+/*
+ * Test IRadioNetwork.triggerEmergencyNetworkScan() for the response returned.
+ */
+TEST_P(RadioNetworkTest, triggerEmergencyNetworkScan) {
+    LOG(DEBUG) << "triggerEmergencyNetworkScan";
+    serial = GetRandomSerialNumber();
+
+    EmergencyNetworkScanTrigger scanRequest;
+    scanRequest.accessNetwork = {AccessNetwork::EUTRAN};
+    scanRequest.scanType = EmergencyScanType::NO_PREFERENCE;
+
+    radio_network->triggerEmergencyNetworkScan(serial, scanRequest);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_network->rspInfo.type);
+    EXPECT_EQ(serial, radioRsp_network->rspInfo.serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(
+            radioRsp_network->rspInfo.error,
+            {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE,
+             RadioError::MODEM_ERR, RadioError::INVALID_ARGUMENTS}));
+    LOG(DEBUG) << "triggerEmergencyNetworkScan finished";
+}
+
+/*
+ * Test IRadioNetwork.cancelEmergencyNetworkScan() for the response returned.
+ */
+TEST_P(RadioNetworkTest, cancelEmergencyNetworkScan) {
+    LOG(DEBUG) << "cancelEmergencyNetworkScan";
+    serial = GetRandomSerialNumber();
+
+    radio_network->cancelEmergencyNetworkScan(serial);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_network->rspInfo.type);
+    EXPECT_EQ(serial, radioRsp_network->rspInfo.serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(
+            radioRsp_network->rspInfo.error,
+            {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE, RadioError::MODEM_ERR}));
+    LOG(DEBUG) << "cancelEmergencyNetworkScan finished";
+}
+
+/*
+ * Test IRadioNetwork.exitEmergencyMode() for the response returned.
+ */
+TEST_P(RadioNetworkTest, exitEmergencyMode) {
+    LOG(DEBUG) << "exitEmergencyMode";
+    serial = GetRandomSerialNumber();
+
+    radio_network->exitEmergencyMode(serial);
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_network->rspInfo.type);
+    EXPECT_EQ(serial, radioRsp_network->rspInfo.serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(
+            radioRsp_network->rspInfo.error,
+            {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE, RadioError::MODEM_ERR}));
+    LOG(DEBUG) << "exitEmergencyMode finished";
+}
diff --git a/radio/aidl/vts/radio_network_utils.h b/radio/aidl/vts/radio_network_utils.h
index 3888a6e..8480825 100644
--- a/radio/aidl/vts/radio_network_utils.h
+++ b/radio/aidl/vts/radio_network_utils.h
@@ -148,10 +148,16 @@
     virtual ndk::ScopedAStatus supplyNetworkDepersonalizationResponse(
             const RadioResponseInfo& info, int32_t remainingRetries) override;
 
-    virtual ndk::ScopedAStatus setEmergencyModeResponse(const RadioResponseInfo& info, const EmergencyRegResult& regState) override;
-    virtual ndk::ScopedAStatus triggerEmergencyNetworkScanResponse(const RadioResponseInfo& info) override;
+    virtual ndk::ScopedAStatus setEmergencyModeResponse(
+            const RadioResponseInfo& info, const EmergencyRegResult& regState) override;
+
+    virtual ndk::ScopedAStatus triggerEmergencyNetworkScanResponse(
+            const RadioResponseInfo& info) override;
+
     virtual ndk::ScopedAStatus exitEmergencyModeResponse(const RadioResponseInfo& info) override;
-    virtual ndk::ScopedAStatus cancelEmergencyNetworkScanResponse(const RadioResponseInfo& info) override;
+
+    virtual ndk::ScopedAStatus cancelEmergencyNetworkScanResponse(
+            const RadioResponseInfo& info) override;
 };
 
 /* Callback class for radio network indication */
@@ -207,8 +213,8 @@
     virtual ndk::ScopedAStatus voiceRadioTechChanged(RadioIndicationType type,
                                                      RadioTechnology rat) override;
 
-    virtual ndk::ScopedAStatus emergencyNetworkScanResult(RadioIndicationType type,
-                                                     const EmergencyRegResult& result) override;
+    virtual ndk::ScopedAStatus emergencyNetworkScanResult(
+            RadioIndicationType type, const EmergencyRegResult& result) override;
 };
 
 // The main test class for Radio AIDL Network.
diff --git a/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl b/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
index 6954d65..6854851 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/DeviceInfo.aidl
@@ -49,8 +49,16 @@
      *         "security_level" : "tee" / "strongbox",
      *         "fused": 1 / 0,  ; 1 if secure boot is enforced for the processor that the IRPC
      *                          ; implementation is contained in. 0 otherwise.
-     *         "cert_type": "widevine" / "keymint"
+     *         "cert_type": CertificateType;
      *     }
+     *
+     *     ; A tstr identifying the type of certificate. The set of supported certificate types may
+     *     ; be extended without requiring a version bump of the HAL. Custom certificate types may
+     *     ; be used, but the provisioning server may reject the request for an unknown certificate
+     *     ; type. The currently defined certificate types are:
+     *     ;  - "widevine"
+     *     ;  - "keymint"
+     *     CertificateType = tstr
      */
     byte[] deviceInfo;
 }
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 926d105..2e4fc15 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -624,9 +624,15 @@
      *
      *   o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token.
      *
-     *   o The timestamp in the auth token plus the value of the Tag::AUTH_TIMEOUT must be less than
-     *     the current secure timestamp (which is a monotonic timer counting milliseconds since
-     *     boot.)
+     *   o If the device has a source of secure time, then the timestamp in the auth token plus the
+     *     value of the Tag::AUTH_TIMEOUT must be greater than the current secure timestamp (which
+     *     is a monotonic timer counting milliseconds since boot).
+     *
+     *   o If the device does not have a source of secure time, then the timestamp check should be
+     *     performed on the first update(), updateAad() or finish() invocation for the operation,
+     *     using the timeStampToken parameter provided on the invocation to indicate the current
+     *     timestamp. It may optionally also be performed on subsequent update() / updateAad() /
+     *     finish() invocations.
      *
      *   If any of these conditions are not met, begin() must return
      *   ErrorCode::KEY_USER_NOT_AUTHENTICATED.
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
index c30c183..82c8a0d 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintOperation.aidl
@@ -40,31 +40,7 @@
      *
      * == Authorization Enforcement ==
      *
-     * Key authorization enforcement is performed primarily in begin().  The one exception is the
-     * case where the key has:
-     *
-     * o One or more Tag::USER_SECURE_IDs, and
-     *
-     * o Does not have a Tag::AUTH_TIMEOUT
-     *
-     * In this case, the key requires an authorization per operation, and the update method must
-     * receive a non-null and valid HardwareAuthToken.  For the auth token to be valid, all of the
-     * following has to be true:
-     *
-     *   o The HMAC field must validate correctly.
-     *
-     *   o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of
-     *     the secure ID values in the token.
-     *
-     *   o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token.
-     *
-     *   o The challenge field in the auth token must contain the value returned from
-     *     IKeyMintDevice::begin(), given by the challenge field of the BeginResult structure.
-     *
-     *   If any of these conditions are not met, updateAad() must return
-     *   ErrorCode::KEY_USER_NOT_AUTHENTICATED.
-     *
-     * The caller must provide the auth token on every call to updateAad(), update() and finish().
+     * See the Authorization Enforcement section for the update() method.
      *
      *
      * For GCM encryption, the AEAD tag must be appended to the ciphertext by finish().  During
@@ -104,16 +80,22 @@
      *
      * == Authorization Enforcement ==
      *
-     * Key authorization enforcement is performed primarily in IKeyMintDevice::begin().  The one
-     * exception is the case where the key has:
+     * Key authorization enforcement is performed primarily in IKeyMintDevice::begin().  There are
+     * two exceptions to this:
      *
-     * o One or more Tag::USER_SECURE_IDs, and
+     *  1) Key with USER_SECURE_IDs but no AUTH_TIMEOUT
      *
-     * o Does not have a Tag::AUTH_TIMEOUT
+     *  2) Key with USER_SECURE_IDs and AUTH_TIMEOUT, but the device does not support secure time.
      *
-     * In this case, the key requires an authorization per operation, and the update method must
-     * receive a non-empty and valid HardwareAuthToken.  For the auth token to be valid, all of the
-     * following has to be true:
+     * The first exception is the case where the key:
+     *
+     *   o Has one or more Tag::USER_SECURE_IDs, and
+     *
+     *   o Does not have a Tag::AUTH_TIMEOUT
+     *
+     * In this case, the key requires an authorization per operation, and update() / updateAad() /
+     * finish() methods must receive a non-null and valid HardwareAuthToken.  For the auth token to
+     * be valid, all of the following has to be true:
      *
      *   o The HMAC field must validate correctly.
      *
@@ -125,10 +107,47 @@
      *   o The challenge field in the auth token must contain the challenge value contained in the
      *     BeginResult returned from IKeyMintDevice::begin().
      *
-     *   If any of these conditions are not met, update() must return
+     *   If any of these conditions are not met, the method must return
      *   ErrorCode::KEY_USER_NOT_AUTHENTICATED.
      *
-     * The caller must provide the auth token on every call to update() and finish().
+     * The caller must provide the auth token on every call to update(), updateAad() and finish().
+     *
+     *
+     * The second exception is the case where the key:
+     *
+     *   o Has one or more Tag::USER_SECURE_IDs, and
+     *
+     *   o Has a Tag::AUTH_TIMEOUT value, but the device does not have a source of secure time (as
+     *     indicated by the KeyMintHardwareInfo.timestampTokenRequired field).
+     *
+     * In this case, the key requires an per-operation authorization on the first call to update(),
+     * updateAad() or finish() for the operation, using the provided timeStampToken as a source of
+     * secure time.  For this timeStampToken to be valid, all of the following has to be true:
+     *
+     *   o The HMAC field must validate correctly.
+     *
+     *   o The challenge field in the auth token must contain the challenge value contained in the
+     *     BeginResult returned from IKeyMintDevice::begin().
+     *
+     * The resulting secure time value is then used to authenticate the HardwareAuthToken. For the
+     * auth token to be valid, all of the following has to be true:
+     *
+     *   o The HMAC field must validate correctly.
+     *
+     *   o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of
+     *     the secure ID values in the token.
+     *
+     *   o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token.
+     *
+     *   o The challenge field in the auth token must contain the challenge value contained in the
+     *     BeginResult returned from IKeyMintDevice::begin().
+     *
+     *   o The timestamp in the auth token plus the value of the Tag::AUTH_TIMEOUT must be greater
+     *     than the provided secure timestamp.
+
+     *   If any of these conditions are not met, the method must return
+     *   ErrorCode::KEY_USER_NOT_AUTHENTICATED.
+     *
      *
      * -- RSA keys --
      *
@@ -187,24 +206,7 @@
      * Key authorization enforcement is performed primarily in begin().  The exceptions are
      * authorization per operation keys and confirmation-required keys.
      *
-     * Authorization per operation keys are the case where the key has one or more
-     * Tag::USER_SECURE_IDs, and does not have a Tag::AUTH_TIMEOUT.  In this case, the key requires
-     * an authorization per operation, and the finish method must receive a non-empty and valid
-     * authToken.  For the auth token to be valid, all of the following has to be true:
-     *
-     *   o The HMAC field must validate correctly.
-     *
-     *   o At least one of the Tag::USER_SECURE_ID values from the key must match at least one of
-     *     the secure ID values in the token.
-     *
-     *   o The key must have a Tag::USER_AUTH_TYPE that matches the auth type in the token.
-     *
-     *   o The challenge field in the auth token must contain the operation challenge.
-     *
-     *   If any of these conditions are not met, update() must return
-     *   ErrorCode::KEY_USER_NOT_AUTHENTICATED.
-     *
-     * The caller must provide the auth token on every call to update() and finish().
+     * Authorization per operation keys must be authorized as described for the update() method.
      *
      * Confirmation-required keys are keys that were generated with
      * Tag::TRUSTED_CONFIRMATION_REQUIRED.  For these keys, when doing a signing operation the
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index c2acbed..3f1f2f7 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -151,7 +151,8 @@
 
     /**
      * This method has been removed in version 3 of the HAL. The header is kept around for
-     * backwards compatibility purposes. Calling this method should return STATUS_REMOVED on v3.
+     * backwards compatibility purposes. From v3, this method should raise a
+     * ServiceSpecificException with an error code of STATUS_REMOVED.
      *
      * For v1 and v2 implementations:
      * generateCertificateRequest creates a certificate request to be sent to the provisioning
@@ -378,8 +379,8 @@
      *                                      ; Last certificate corresponds to KeyMint's DICE key.
      * ]
      *
-     * ; This is the signed payload for each entry in the DCC. Note that the "Configuration
-     * ; Input Values" described by the Open Profile are not used here. Instead, the Dcc
+     * ; This is the signed payload for each entry in the DICE chain. Note that the "Configuration
+     * ; Input Values" described by the Open Profile are not used here. Instead, the DICE chain
      * ; defines its own configuration values for the Configuration Descriptor field. See
      * ; the Open Profile for DICE for more details on the fields. SHA256 and SHA512 are acceptable
      * ; hash algorithms. The digest bstr values in the payload are the digest values without any
@@ -408,8 +409,8 @@
      *     -4670551 : bstr,                         ; Mode
      * }
      *
-     * ; Each entry in the Dcc is a DiceChainEntryPayload signed by the key from the previous entry
-     * ; in the Dcc array.
+     * ; Each entry in the DICE chain is a DiceChainEntryPayload signed by the key from the previous
+     * ; entry in the DICE chain array.
      * DiceChainEntry = [                            ; COSE_Sign1 (untagged)
      *     protected : bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
      *     unprotected: {},
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index 2e282e0..e1f65fe 100644
--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <memory>
+#include <string>
 #define LOG_TAG "VtsRemotelyProvisionableComponentTests"
 
 #include <AndroidRemotelyProvisionedComponentDevice.h>
@@ -58,26 +60,6 @@
 using namespace remote_prov;
 using namespace keymaster;
 
-std::set<std::string> getAllowedVbStates() {
-    return {"green", "yellow", "orange"};
-}
-
-std::set<std::string> getAllowedBootloaderStates() {
-    return {"locked", "unlocked"};
-}
-
-std::set<std::string> getAllowedSecurityLevels() {
-    return {"tee", "strongbox"};
-}
-
-std::set<std::string> getAllowedAttIdStates() {
-    return {"locked", "open"};
-}
-
-std::set<std::string> getAttestationIdEntrySet() {
-    return {"brand", "manufacturer", "product", "model", "device"};
-}
-
 bytevec string_to_bytevec(const char* s) {
     const uint8_t* p = reinterpret_cast<const uint8_t*>(s);
     return bytevec(p, p + strlen(s));
@@ -387,177 +369,6 @@
         }
     }
 
-    ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey) {
-        if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
-            rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
-            return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
-                                         senderPubkey->first, false /* senderIsA */);
-        } else {
-            return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey,
-                                       senderPubkey->first, false /* senderIsA */);
-        }
-    }
-
-    void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
-                            const bytevec& keysToSignMac, const ProtectedData& protectedData,
-                            std::vector<BccEntryData>* bccOutput = nullptr) {
-        auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
-        ASSERT_TRUE(parsedProtectedData) << protDataErrMsg;
-        ASSERT_TRUE(parsedProtectedData->asArray());
-        ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount);
-
-        auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
-        ASSERT_TRUE(senderPubkey) << senderPubkey.message();
-        EXPECT_EQ(senderPubkey->second, eekId_);
-
-        auto sessionKey = getSessionKey(senderPubkey);
-        ASSERT_TRUE(sessionKey) << sessionKey.message();
-
-        auto protectedDataPayload =
-                decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
-        ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message();
-
-        auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
-        ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg;
-        ASSERT_TRUE(parsedPayload->asArray());
-        // Strongbox may contain additional certificate chain.
-        EXPECT_LE(parsedPayload->asArray()->size(), 3U);
-
-        auto& signedMac = parsedPayload->asArray()->get(0);
-        auto& bcc = parsedPayload->asArray()->get(1);
-        ASSERT_TRUE(signedMac && signedMac->asArray());
-        ASSERT_TRUE(bcc && bcc->asArray());
-
-        // BCC is [ pubkey, + BccEntry]
-        auto bccContents = validateBcc(bcc->asArray());
-        ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get());
-        ASSERT_GT(bccContents->size(), 0U);
-
-        auto [deviceInfoMap, __2, deviceInfoErrMsg] = cppbor::parse(deviceInfo.deviceInfo);
-        ASSERT_TRUE(deviceInfoMap) << "Failed to parse deviceInfo: " << deviceInfoErrMsg;
-        ASSERT_TRUE(deviceInfoMap->asMap());
-        checkDeviceInfo(*deviceInfoMap->asMap(), deviceInfo.deviceInfo);
-        auto& signingKey = bccContents->back().pubKey;
-        deviceInfoMap->asMap()->canonicalize();
-        auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
-                                              cppbor::Array()  // SignedMacAad
-                                                      .add(challenge_)
-                                                      .add(std::move(deviceInfoMap))
-                                                      .add(keysToSignMac)
-                                                      .encode());
-        ASSERT_TRUE(macKey) << macKey.message();
-
-        auto coseMac0 = cppbor::Array()
-                                .add(cppbor::Map()  // protected
-                                             .add(ALGORITHM, HMAC_256)
-                                             .canonicalize()
-                                             .encode())
-                                .add(cppbor::Map())        // unprotected
-                                .add(keysToSign.encode())  // payload (keysToSign)
-                                .add(keysToSignMac);       // tag
-
-        auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
-        ASSERT_TRUE(macPayload) << macPayload.message();
-
-        if (bccOutput) {
-            *bccOutput = std::move(*bccContents);
-        }
-    }
-
-    std::optional<std::string> assertAttribute(const cppbor::Map& devInfo,
-                                               cppbor::MajorType majorType, std::string entryName) {
-        const auto& val = devInfo.get(entryName);
-        if (!val) return entryName + " is missing.\n";
-        if (val->type() != majorType) return entryName + " has the wrong type.\n";
-        switch (majorType) {
-            case cppbor::TSTR:
-                if (val->asTstr()->value().size() <= 0) {
-                    return entryName + " is present but the value is empty.\n";
-                }
-                break;
-            case cppbor::BSTR:
-                if (val->asBstr()->value().size() <= 0) {
-                    return entryName + " is present but the value is empty.\n";
-                }
-                break;
-            default:
-                break;
-        }
-        return {};
-    }
-
-    void checkType(const cppbor::Map& devInfo, cppbor::MajorType majorType, std::string entryName) {
-        if (auto error = assertAttribute(devInfo, majorType, entryName)) {
-            FAIL() << *error;
-        }
-    }
-
-    void checkDeviceInfo(const cppbor::Map& deviceInfo, bytevec deviceInfoBytes) {
-        EXPECT_EQ(deviceInfo.clone()->asMap()->canonicalize().encode(), deviceInfoBytes)
-                << "DeviceInfo ordering is non-canonical.";
-        const auto& version = deviceInfo.get("version");
-        ASSERT_TRUE(version);
-        ASSERT_TRUE(version->asUint());
-        RpcHardwareInfo info;
-        provisionable_->getHardwareInfo(&info);
-        ASSERT_EQ(version->asUint()->value(), info.versionNumber);
-        std::set<std::string> allowList;
-        std::string problemEntries;
-        switch (version->asUint()->value()) {
-            // These fields became mandated in version 2.
-            case 2:
-                for (auto attId : getAttestationIdEntrySet()) {
-                    if (auto errMsg = assertAttribute(deviceInfo, cppbor::TSTR, attId)) {
-                        problemEntries += *errMsg;
-                    }
-                }
-                EXPECT_EQ("", problemEntries)
-                        << problemEntries
-                        << "Attestation IDs are missing or malprovisioned. If this test is being "
-                           "run against an early proto or EVT build, this error is probably WAI "
-                           "and indicates that Device IDs were not provisioned in the factory. If "
-                           "this error is returned on a DVT or later build revision, then "
-                           "something is likely wrong with the factory provisioning process.";
-                // TODO: Refactor the KeyMint code that validates these fields and include it here.
-                checkType(deviceInfo, cppbor::TSTR, "vb_state");
-                allowList = getAllowedVbStates();
-                EXPECT_NE(allowList.find(deviceInfo.get("vb_state")->asTstr()->value()),
-                          allowList.end());
-                checkType(deviceInfo, cppbor::TSTR, "bootloader_state");
-                allowList = getAllowedBootloaderStates();
-                EXPECT_NE(allowList.find(deviceInfo.get("bootloader_state")->asTstr()->value()),
-                          allowList.end());
-                checkType(deviceInfo, cppbor::BSTR, "vbmeta_digest");
-                checkType(deviceInfo, cppbor::UINT, "system_patch_level");
-                checkType(deviceInfo, cppbor::UINT, "boot_patch_level");
-                checkType(deviceInfo, cppbor::UINT, "vendor_patch_level");
-                checkType(deviceInfo, cppbor::UINT, "fused");
-                EXPECT_LT(deviceInfo.get("fused")->asUint()->value(), 2);  // Must be 0 or 1.
-                checkType(deviceInfo, cppbor::TSTR, "security_level");
-                allowList = getAllowedSecurityLevels();
-                EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()),
-                          allowList.end());
-                if (deviceInfo.get("security_level")->asTstr()->value() == "tee") {
-                    checkType(deviceInfo, cppbor::TSTR, "os_version");
-                }
-                break;
-            case 1:
-                checkType(deviceInfo, cppbor::TSTR, "security_level");
-                allowList = getAllowedSecurityLevels();
-                EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()),
-                          allowList.end());
-                if (version->asUint()->value() == 1) {
-                    checkType(deviceInfo, cppbor::TSTR, "att_id_state");
-                    allowList = getAllowedAttIdStates();
-                    EXPECT_NE(allowList.find(deviceInfo.get("att_id_state")->asTstr()->value()),
-                              allowList.end());
-                }
-                break;
-            default:
-                FAIL() << "Unrecognized version: " << version->asUint()->value();
-        }
-    }
-
     bytevec eekId_;
     size_t testEekLength_;
     EekChain testEekChain_;
@@ -584,7 +395,10 @@
                 &protectedData, &keysToSignMac);
         ASSERT_TRUE(status.isOk()) << status.getMessage();
 
-        checkProtectedData(deviceInfo, cppbor::Array(), keysToSignMac, protectedData);
+        auto result = verifyProductionProtectedData(
+                deviceInfo, cppbor::Array(), keysToSignMac, protectedData, testEekChain_, eekId_,
+                rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+        ASSERT_TRUE(result) << result.message();
     }
 }
 
@@ -606,22 +420,24 @@
             &protectedData, &keysToSignMac);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
 
-    std::vector<BccEntryData> firstBcc;
-    checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
-                       &firstBcc);
+    auto firstBcc = verifyProductionProtectedData(
+            deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
+            eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+    ASSERT_TRUE(firstBcc) << firstBcc.message();
 
     status = provisionable_->generateCertificateRequest(
             testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo,
             &protectedData, &keysToSignMac);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
 
-    std::vector<BccEntryData> secondBcc;
-    checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData,
-                       &secondBcc);
+    auto secondBcc = verifyProductionProtectedData(
+            deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_,
+            eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+    ASSERT_TRUE(secondBcc) << secondBcc.message();
 
     // Verify that none of the keys in the first BCC are repeated in the second one.
-    for (const auto& i : firstBcc) {
-        for (auto& j : secondBcc) {
+    for (const auto& i : *firstBcc) {
+        for (auto& j : *secondBcc) {
             ASSERT_THAT(i.pubKey, testing::Not(testing::ElementsAreArray(j.pubKey)))
                     << "Found a repeated pubkey in two generateCertificateRequest test mode calls";
         }
@@ -664,7 +480,10 @@
                 &keysToSignMac);
         ASSERT_TRUE(status.isOk()) << status.getMessage();
 
-        checkProtectedData(deviceInfo, cborKeysToSign_, keysToSignMac, protectedData);
+        auto result = verifyProductionProtectedData(
+                deviceInfo, cborKeysToSign_, keysToSignMac, protectedData, testEekChain_, eekId_,
+                rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_);
+        ASSERT_TRUE(result) << result.message();
     }
 }
 
diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp
index bf2ab02..3f48320 100644
--- a/security/keymint/support/Android.bp
+++ b/security/keymint/support/Android.bp
@@ -65,6 +65,7 @@
     ],
     shared_libs: [
         "libbase",
+        "libbinder_ndk",
         "libcppbor_external",
         "libcppcose_rkp",
         "libcrypto",
diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h
index f3b8608..9ea20ac 100644
--- a/security/keymint/support/include/remote_prov/remote_prov_utils.h
+++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h
@@ -16,7 +16,9 @@
 
 #pragma once
 
+#include <memory>
 #include <vector>
+#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
 
 #include <keymaster/cppcose/cppcose.h>
 
@@ -139,4 +141,40 @@
 JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name,
                                   const cppbor::Array& csr);
 
+/**
+ * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
+ * ensure it contains the minimum required data at the time of manufacturing. This is only a
+ * partial validation, as some fields may not be provisioned yet at the time this information
+ * is parsed in the manufacturing process.
+ */
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
+        const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
+
+/**
+ * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to
+ * ensure it is formatted correctly and that it contains the required values for Remote Key
+ * Provisioning. This is a full validation, and assumes the device is provisioned as if it is
+ * suitable for the end user.
+ */
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
+        const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable);
+
+/**
+ * Verify the protected data as if the device is still early in the factory process and may not
+ * have all device identifiers provisioned yet.
+ */
+ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
+        const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+        const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+        const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+        IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
+/**
+ * Verify the protected data as if the device is a final production sample.
+ */
+ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
+        const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+        const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+        const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+        IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge);
+
 }  // namespace aidl::android::hardware::security::keymint::remote_prov
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
index 0dbea5b..0651496 100644
--- a/security/keymint/support/remote_prov_utils.cpp
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -15,7 +15,11 @@
  */
 
 #include <iterator>
+#include <memory>
+#include <set>
+#include <string>
 #include <tuple>
+#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h"
 
 #include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
 #include <android-base/properties.h>
@@ -441,4 +445,293 @@
     return JsonOutput::Ok(Json::writeString(factory, json));
 }
 
-}  // namespace aidl::android::hardware::security::keymint::remote_prov
+std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
+                          const std::string& entryName) {
+    const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
+    if (!val) {
+        return entryName + " is missing.\n";
+    }
+    if (val->type() != majorType) {
+        return entryName + " has the wrong type.\n";
+    }
+    if (isFactory) {
+        return "";
+    }
+    switch (majorType) {
+        case cppbor::TSTR:
+            if (val->asTstr()->value().size() <= 0) {
+                return entryName + " is present but the value is empty.\n";
+            }
+            break;
+        case cppbor::BSTR:
+            if (val->asBstr()->value().size() <= 0) {
+                return entryName + " is present but the value is empty.\n";
+            }
+            break;
+        default:
+            break;
+    }
+    return "";
+}
+
+std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType,
+                          const std::string& entryName, const cppbor::Array& allowList) {
+    std::string error = checkMapEntry(isFactory, devInfo, majorType, entryName);
+    if (!error.empty()) {
+        return error;
+    }
+
+    if (isFactory) {
+        return "";
+    }
+
+    const std::unique_ptr<cppbor::Item>& val = devInfo.get(entryName);
+    for (auto i = allowList.begin(); i != allowList.end(); ++i) {
+        if (**i == *val) {
+            return "";
+        }
+    }
+    return entryName + " has an invalid value.\n";
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateDeviceInfo(
+        const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable,
+        bool isFactory) {
+    const cppbor::Array kValidVbStates = {"green", "yellow", "orange"};
+    const cppbor::Array kValidBootloaderStates = {"locked", "unlocked"};
+    const cppbor::Array kValidSecurityLevels = {"tee", "strongbox"};
+    const cppbor::Array kValidAttIdStates = {"locked", "open"};
+    const cppbor::Array kValidFused = {0, 1};
+
+    struct AttestationIdEntry {
+        const char* id;
+        bool alwaysValidate;
+    };
+    constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false},
+                                                             {"manufacturer", true},
+                                                             {"product", false},
+                                                             {"model", false},
+                                                             {"device", false}};
+
+    auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = cppbor::parse(deviceInfoBytes);
+    if (!parsedVerifiedDeviceInfo) {
+        return errMsg;
+    }
+
+    std::unique_ptr<cppbor::Map> parsed(parsedVerifiedDeviceInfo->asMap());
+    if (!parsed) {
+        return "DeviceInfo must be a CBOR map.";
+    }
+    parsedVerifiedDeviceInfo.release();
+
+    if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) {
+        return "DeviceInfo ordering is non-canonical.";
+    }
+    const std::unique_ptr<cppbor::Item>& version = parsed->get("version");
+    if (!version) {
+        return "Device info is missing version";
+    }
+    if (!version->asUint()) {
+        return "version must be an unsigned integer";
+    }
+    RpcHardwareInfo info;
+    provisionable->getHardwareInfo(&info);
+    if (version->asUint()->value() != info.versionNumber) {
+        return "DeviceInfo version (" + std::to_string(version->asUint()->value()) +
+               ") does not match the remotely provisioned component version (" +
+               std::to_string(info.versionNumber) + ").";
+    }
+    std::string error;
+    switch (version->asUint()->value()) {
+        case 2:
+            for (const auto& entry : kAttestationIdEntrySet) {
+                error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR,
+                                       entry.id);
+            }
+            if (!error.empty()) {
+                return error +
+                       "Attestation IDs are missing or malprovisioned. If this test is being\n"
+                       "run against an early proto or EVT build, this error is probably WAI\n"
+                       "and indicates that Device IDs were not provisioned in the factory. If\n"
+                       "this error is returned on a DVT or later build revision, then\n"
+                       "something is likely wrong with the factory provisioning process.";
+            }
+            // TODO: Refactor the KeyMint code that validates these fields and include it here.
+            error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "vb_state", kValidVbStates);
+            error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "bootloader_state",
+                                   kValidBootloaderStates);
+            error += checkMapEntry(isFactory, *parsed, cppbor::BSTR, "vbmeta_digest");
+            error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "system_patch_level");
+            error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "boot_patch_level");
+            error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "vendor_patch_level");
+            error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "fused", kValidFused);
+            error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
+                                   kValidSecurityLevels);
+            if (parsed->get("security_level") && parsed->get("security_level")->asTstr() &&
+                parsed->get("security_level")->asTstr()->value() == "tee") {
+                error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "os_version");
+            }
+            break;
+        case 1:
+            error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level",
+                                   kValidSecurityLevels);
+            error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "att_id_state",
+                                   kValidAttIdStates);
+            break;
+        default:
+            return "Unrecognized version: " + std::to_string(version->asUint()->value());
+    }
+
+    if (!error.empty()) {
+        return error;
+    }
+
+    return std::move(parsed);
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateFactoryDeviceInfo(
+        const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
+    return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/true);
+}
+
+ErrMsgOr<std::unique_ptr<cppbor::Map>> parseAndValidateProductionDeviceInfo(
+        const std::vector<uint8_t>& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) {
+    return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/false);
+}
+
+ErrMsgOr<bytevec> getSessionKey(ErrMsgOr<std::pair<bytevec, bytevec>>& senderPubkey,
+                                const EekChain& eekChain, int32_t supportedEekCurve) {
+    if (supportedEekCurve == RpcHardwareInfo::CURVE_25519 ||
+        supportedEekCurve == RpcHardwareInfo::CURVE_NONE) {
+        return x25519_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey,
+                                     senderPubkey->first, false /* senderIsA */);
+    } else {
+        return ECDH_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey, senderPubkey->first,
+                                   false /* senderIsA */);
+    }
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyProtectedData(
+        const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+        const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+        const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+        IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge,
+        bool isFactory) {
+    auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData);
+    if (!parsedProtectedData) {
+        return protDataErrMsg;
+    }
+    if (!parsedProtectedData->asArray()) {
+        return "Protected data is not a CBOR array.";
+    }
+    if (parsedProtectedData->asArray()->size() != kCoseEncryptEntryCount) {
+        return "The protected data COSE_encrypt structure must have " +
+               std::to_string(kCoseEncryptEntryCount) + " entries, but it only has " +
+               std::to_string(parsedProtectedData->asArray()->size());
+    }
+
+    auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData);
+    if (!senderPubkey) {
+        return senderPubkey.message();
+    }
+    if (senderPubkey->second != eekId) {
+        return "The COSE_encrypt recipient does not match the expected EEK identifier";
+    }
+
+    auto sessionKey = getSessionKey(senderPubkey, eekChain, supportedEekCurve);
+    if (!sessionKey) {
+        return sessionKey.message();
+    }
+
+    auto protectedDataPayload =
+            decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */);
+    if (!protectedDataPayload) {
+        return protectedDataPayload.message();
+    }
+
+    auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload);
+    if (!parsedPayload) {
+        return "Failed to parse payload: " + payloadErrMsg;
+    }
+    if (!parsedPayload->asArray()) {
+        return "The protected data payload must be an Array.";
+    }
+    if (parsedPayload->asArray()->size() != 3U && parsedPayload->asArray()->size() != 2U) {
+        return "The protected data payload must contain SignedMAC and BCC. It may optionally "
+               "contain AdditionalDKSignatures. However, the parsed payload has " +
+               std::to_string(parsedPayload->asArray()->size()) + " entries.";
+    }
+
+    auto& signedMac = parsedPayload->asArray()->get(0);
+    auto& bcc = parsedPayload->asArray()->get(1);
+    if (!signedMac->asArray()) {
+        return "The SignedMAC in the protected data payload is not an Array.";
+    }
+    if (!bcc->asArray()) {
+        return "The BCC in the protected data payload is not an Array.";
+    }
+
+    // BCC is [ pubkey, + BccEntry]
+    auto bccContents = validateBcc(bcc->asArray());
+    if (!bccContents) {
+        return bccContents.message() + "\n" + prettyPrint(bcc.get());
+    }
+    if (bccContents->size() == 0U) {
+        return "The BCC is empty. It must contain at least one entry.";
+    }
+
+    auto deviceInfoResult =
+            parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable, isFactory);
+    if (!deviceInfoResult) {
+        return deviceInfoResult.message();
+    }
+    std::unique_ptr<cppbor::Map> deviceInfoMap = deviceInfoResult.moveValue();
+    auto& signingKey = bccContents->back().pubKey;
+    auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey,
+                                          cppbor::Array()  // SignedMacAad
+                                                  .add(challenge)
+                                                  .add(std::move(deviceInfoMap))
+                                                  .add(keysToSignMac)
+                                                  .encode());
+    if (!macKey) {
+        return macKey.message();
+    }
+
+    auto coseMac0 = cppbor::Array()
+                            .add(cppbor::Map()  // protected
+                                         .add(ALGORITHM, HMAC_256)
+                                         .canonicalize()
+                                         .encode())
+                            .add(cppbor::Map())        // unprotected
+                            .add(keysToSign.encode())  // payload (keysToSign)
+                            .add(keysToSignMac);       // tag
+
+    auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey);
+    if (!macPayload) {
+        return macPayload.message();
+    }
+
+    return *bccContents;
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyFactoryProtectedData(
+        const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+        const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+        const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+        IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
+    return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
+                               eekId, supportedEekCurve, provisionable, challenge,
+                               /*isFactory=*/true);
+}
+
+ErrMsgOr<std::vector<BccEntryData>> verifyProductionProtectedData(
+        const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign,
+        const std::vector<uint8_t>& keysToSignMac, const ProtectedData& protectedData,
+        const EekChain& eekChain, const std::vector<uint8_t>& eekId, int32_t supportedEekCurve,
+        IRemotelyProvisionedComponent* provisionable, const std::vector<uint8_t>& challenge) {
+    return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain,
+                               eekId, supportedEekCurve, provisionable, challenge,
+                               /*isFactory=*/false);
+}
+
+}  // namespace aidl::android::hardware::security::keymint::remote_prov
\ No newline at end of file
diff --git a/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp b/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
index 44fa3be..e8ed26a 100644
--- a/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
+++ b/vibrator/aidl/vts/VtsHalVibratorManagerTargetTest.cpp
@@ -96,6 +96,7 @@
     if (!(capabilities & IVibratorManager::CAP_SYNC)) return;
     if (vibratorIds.empty()) return;
     EXPECT_TRUE(manager->prepareSynced(vibratorIds).isOk());
+    EXPECT_TRUE(manager->cancelSynced().isOk());
 }
 
 TEST_P(VibratorAidl, PrepareSyncedEmptySetIsInvalid) {
@@ -208,6 +209,7 @@
         EXPECT_TRUE(manager->prepareSynced(vibratorIds).isOk());
         Status status = manager->triggerSynced(callback);
         EXPECT_TRUE(isUnknownOrUnsupported(status)) << status;
+        EXPECT_TRUE(manager->cancelSynced().isOk());
     }
 }
 
diff --git a/wifi/netlinkinterceptor/libnlinterceptor/Android.bp b/wifi/netlinkinterceptor/libnlinterceptor/Android.bp
index 00cae32..671cd85 100644
--- a/wifi/netlinkinterceptor/libnlinterceptor/Android.bp
+++ b/wifi/netlinkinterceptor/libnlinterceptor/Android.bp
@@ -37,10 +37,8 @@
         "libutils",
     ],
     sanitize: {
-        address: true,
         undefined: true,
         all_undefined: true,
-        fuzzer: true,
         cfi: true,
         integer_overflow: true,
         scs: true,