Merge "Added resolution to SubscribeOptions." into main
diff --git a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
index e53947e..3fecbd9 100644
--- a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
@@ -118,7 +118,7 @@
     virtual aidl::android::hardware::automotive::vehicle::StatusCode checkHealth() = 0;
 
     // Register a callback that would be called when there is a property change event from vehicle.
-    // Must only be called once during initialization.
+    // This function must only be called once during initialization.
     virtual void registerOnPropertyChangeEvent(
             std::unique_ptr<const PropertyChangeCallback> callback) = 0;
 
@@ -126,6 +126,25 @@
     // vehicle. Must only be called once during initialization.
     virtual void registerOnPropertySetErrorEvent(
             std::unique_ptr<const PropertySetErrorCallback> callback) = 0;
+
+    // Gets the batching window used by DefaultVehicleHal for property change events.
+    //
+    // In DefaultVehicleHal, all the property change events generated within the batching window
+    // will be delivered through one callback to the VHAL client. This affects the maximum supported
+    // subscription rate. For example, if this returns 10ms, then only one callback for property
+    // change events will be called per 10ms, meaining that the max subscription rate for all
+    // continuous properties would be 100hz.
+    //
+    // A higher batching window means less callbacks to the VHAL client, causing a better
+    // performance. However, it also means a longer average latency for every property change
+    // events.
+    //
+    // 0 means no batching should be enabled in DefaultVehicleHal. In this case, batching can
+    // be optionally implemented in IVehicleHardware layer.
+    virtual std::chrono::nanoseconds getPropertyOnChangeEventBatchingWindow() {
+        // By default batching is disabled.
+        return std::chrono::nanoseconds(0);
+    }
 };
 
 }  // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h b/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
index 327c0dc..b636aa3 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
@@ -69,6 +69,19 @@
         mCond.notify_one();
     }
 
+    void push(std::vector<T>&& items) {
+        {
+            std::scoped_lock<std::mutex> lockGuard(mLock);
+            if (!mIsActive) {
+                return;
+            }
+            for (T& item : items) {
+                mQueue.push(std::move(item));
+            }
+        }
+        mCond.notify_one();
+    }
+
     // Deactivates the queue, thus no one can push items to it, also notifies all waiting thread.
     // The items already in the queue could still be flushed even after the queue is deactivated.
     void deactivate() {
@@ -92,6 +105,69 @@
     std::queue<T> mQueue GUARDED_BY(mLock);
 };
 
+template <typename T>
+class BatchingConsumer {
+  private:
+    enum class State {
+        INIT = 0,
+        RUNNING = 1,
+        STOP_REQUESTED = 2,
+        STOPPED = 3,
+    };
+
+  public:
+    BatchingConsumer() : mState(State::INIT) {}
+
+    BatchingConsumer(const BatchingConsumer&) = delete;
+    BatchingConsumer& operator=(const BatchingConsumer&) = delete;
+
+    using OnBatchReceivedFunc = std::function<void(std::vector<T> vec)>;
+
+    void run(ConcurrentQueue<T>* queue, std::chrono::nanoseconds batchInterval,
+             const OnBatchReceivedFunc& func) {
+        mQueue = queue;
+        mBatchInterval = batchInterval;
+
+        mWorkerThread = std::thread(&BatchingConsumer<T>::runInternal, this, func);
+    }
+
+    void requestStop() { mState = State::STOP_REQUESTED; }
+
+    void waitStopped() {
+        if (mWorkerThread.joinable()) {
+            mWorkerThread.join();
+        }
+    }
+
+  private:
+    void runInternal(const OnBatchReceivedFunc& onBatchReceived) {
+        if (mState.exchange(State::RUNNING) == State::INIT) {
+            while (State::RUNNING == mState) {
+                mQueue->waitForItems();
+                if (State::STOP_REQUESTED == mState) break;
+
+                std::this_thread::sleep_for(mBatchInterval);
+                if (State::STOP_REQUESTED == mState) break;
+
+                std::vector<T> items = mQueue->flush();
+
+                if (items.size() > 0) {
+                    onBatchReceived(std::move(items));
+                }
+            }
+        }
+
+        mState = State::STOPPED;
+    }
+
+  private:
+    std::thread mWorkerThread;
+
+    std::atomic<State> mState;
+    std::chrono::nanoseconds mBatchInterval;
+    ConcurrentQueue<T>* mQueue;
+};
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
index 419e16b..9a42180 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
@@ -144,6 +144,15 @@
     std::shared_ptr<PendingRequestPool> mPendingRequestPool;
     // SubscriptionManager is thread-safe.
     std::shared_ptr<SubscriptionManager> mSubscriptionManager;
+    // ConcurrentQueue is thread-safe.
+    std::shared_ptr<ConcurrentQueue<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
+            mBatchedEventQueue;
+    // BatchingConsumer is thread-safe.
+    std::shared_ptr<
+            BatchingConsumer<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
+            mPropertyChangeEventsBatchingConsumer;
+    // Only set once during initialization.
+    std::chrono::nanoseconds mEventBatchingWindow;
 
     std::mutex mLock;
     std::unordered_map<const AIBinder*, std::unique_ptr<OnBinderDiedContext>> mOnBinderDiedContexts
@@ -209,6 +218,19 @@
 
     size_t countSubscribeClients();
 
+    // Handles the property change events in batch.
+    void handleBatchedPropertyEvents(
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
+                    batchedEvents);
+
+    // Puts the property change events into a queue so that they can handled in batch.
+    static void batchPropertyChangeEvent(
+            const std::weak_ptr<ConcurrentQueue<
+                    aidl::android::hardware::automotive::vehicle::VehiclePropValue>>&
+                    batchedEventQueue,
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
+                    updatedValues);
+
     // Gets or creates a {@code T} object for the client to or from {@code clients}.
     template <class T>
     static std::shared_ptr<T> getOrCreateClient(
@@ -217,7 +239,7 @@
 
     static void onPropertyChangeEvent(
             const std::weak_ptr<SubscriptionManager>& subscriptionManager,
-            const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
                     updatedValues);
 
     static void onPropertySetErrorEvent(
diff --git a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
index 512d906..b91895e 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
@@ -92,11 +92,10 @@
     // For a list of updated properties, returns a map that maps clients subscribing to
     // the updated properties to a list of updated values. This would only return on-change property
     // clients that should be informed for the given updated values.
-    std::unordered_map<
-            CallbackType,
-            std::vector<const aidl::android::hardware::automotive::vehicle::VehiclePropValue*>>
+    std::unordered_map<CallbackType,
+                       std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
     getSubscribedClients(
-            const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
                     updatedValues);
 
     // For a list of set property error events, returns a map that maps clients subscribing to the
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index f3eba2e..111a6ec 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -32,6 +32,7 @@
 #include <utils/Trace.h>
 
 #include <inttypes.h>
+#include <chrono>
 #include <set>
 #include <unordered_set>
 
@@ -101,12 +102,32 @@
 
     IVehicleHardware* vehicleHardwarePtr = mVehicleHardware.get();
     mSubscriptionManager = std::make_shared<SubscriptionManager>(vehicleHardwarePtr);
+    mEventBatchingWindow = mVehicleHardware->getPropertyOnChangeEventBatchingWindow();
+    if (mEventBatchingWindow != std::chrono::nanoseconds(0)) {
+        mBatchedEventQueue = std::make_shared<ConcurrentQueue<VehiclePropValue>>();
+        mPropertyChangeEventsBatchingConsumer =
+                std::make_shared<BatchingConsumer<VehiclePropValue>>();
+        mPropertyChangeEventsBatchingConsumer->run(
+                mBatchedEventQueue.get(), mEventBatchingWindow,
+                [this](std::vector<VehiclePropValue> batchedEvents) {
+                    handleBatchedPropertyEvents(std::move(batchedEvents));
+                });
+    }
 
+    std::weak_ptr<ConcurrentQueue<VehiclePropValue>> batchedEventQueueCopy = mBatchedEventQueue;
+    std::chrono::nanoseconds eventBatchingWindow = mEventBatchingWindow;
     std::weak_ptr<SubscriptionManager> subscriptionManagerCopy = mSubscriptionManager;
     mVehicleHardware->registerOnPropertyChangeEvent(
             std::make_unique<IVehicleHardware::PropertyChangeCallback>(
-                    [subscriptionManagerCopy](std::vector<VehiclePropValue> updatedValues) {
-                        onPropertyChangeEvent(subscriptionManagerCopy, updatedValues);
+                    [subscriptionManagerCopy, batchedEventQueueCopy,
+                     eventBatchingWindow](std::vector<VehiclePropValue> updatedValues) {
+                        if (eventBatchingWindow != std::chrono::nanoseconds(0)) {
+                            batchPropertyChangeEvent(batchedEventQueueCopy,
+                                                     std::move(updatedValues));
+                        } else {
+                            onPropertyChangeEvent(subscriptionManagerCopy,
+                                                  std::move(updatedValues));
+                        }
                     }));
     mVehicleHardware->registerOnPropertySetErrorEvent(
             std::make_unique<IVehicleHardware::PropertySetErrorCallback>(
@@ -139,26 +160,47 @@
     // mRecurrentAction uses pointer to mVehicleHardware, so it has to be unregistered before
     // mVehicleHardware.
     mRecurrentTimer.unregisterTimerCallback(mRecurrentAction);
+
+    if (mBatchedEventQueue) {
+        // mPropertyChangeEventsBatchingConsumer uses mSubscriptionManager and mBatchedEventQueue.
+        mBatchedEventQueue->deactivate();
+        mPropertyChangeEventsBatchingConsumer->requestStop();
+        mPropertyChangeEventsBatchingConsumer->waitStopped();
+        mPropertyChangeEventsBatchingConsumer.reset();
+        mBatchedEventQueue.reset();
+    }
+
     // mSubscriptionManager uses pointer to mVehicleHardware, so it has to be destroyed before
     // mVehicleHardware.
     mSubscriptionManager.reset();
     mVehicleHardware.reset();
 }
 
+void DefaultVehicleHal::batchPropertyChangeEvent(
+        const std::weak_ptr<ConcurrentQueue<VehiclePropValue>>& batchedEventQueue,
+        std::vector<VehiclePropValue>&& updatedValues) {
+    auto batchedEventQueueStrong = batchedEventQueue.lock();
+    if (batchedEventQueueStrong == nullptr) {
+        ALOGW("the batched property events queue is destroyed, DefaultVehicleHal is ending");
+        return;
+    }
+    batchedEventQueueStrong->push(std::move(updatedValues));
+}
+
+void DefaultVehicleHal::handleBatchedPropertyEvents(std::vector<VehiclePropValue>&& batchedEvents) {
+    onPropertyChangeEvent(mSubscriptionManager, std::move(batchedEvents));
+}
+
 void DefaultVehicleHal::onPropertyChangeEvent(
         const std::weak_ptr<SubscriptionManager>& subscriptionManager,
-        const std::vector<VehiclePropValue>& updatedValues) {
+        std::vector<VehiclePropValue>&& updatedValues) {
     auto manager = subscriptionManager.lock();
     if (manager == nullptr) {
         ALOGW("the SubscriptionManager is destroyed, DefaultVehicleHal is ending");
         return;
     }
-    auto updatedValuesByClients = manager->getSubscribedClients(updatedValues);
-    for (const auto& [callback, valuePtrs] : updatedValuesByClients) {
-        std::vector<VehiclePropValue> values;
-        for (const VehiclePropValue* valuePtr : valuePtrs) {
-            values.push_back(*valuePtr);
-        }
+    auto updatedValuesByClients = manager->getSubscribedClients(std::move(updatedValues));
+    for (auto& [callback, values] : updatedValuesByClients) {
         SubscriptionClient::sendUpdatedValues(callback, std::move(values));
     }
 }
@@ -742,12 +784,12 @@
         return;
     }
     std::vector<VehiclePropValue> values = {{
-            .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
             .areaId = 0,
+            .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
             .status = VehiclePropertyStatus::AVAILABLE,
             .value.int64Values = {uptimeMillis()},
     }};
-    onPropertyChangeEvent(subscriptionManager, values);
+    onPropertyChangeEvent(subscriptionManager, std::move(values));
     return;
 }
 
diff --git a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
index 17683ae..b3c2693 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
@@ -248,13 +248,12 @@
     return {};
 }
 
-std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<const VehiclePropValue*>>
-SubscriptionManager::getSubscribedClients(const std::vector<VehiclePropValue>& updatedValues) {
+std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>>
+SubscriptionManager::getSubscribedClients(std::vector<VehiclePropValue>&& updatedValues) {
     std::scoped_lock<std::mutex> lockGuard(mLock);
-    std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<const VehiclePropValue*>>
-            clients;
+    std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>> clients;
 
-    for (const auto& value : updatedValues) {
+    for (auto& value : updatedValues) {
         PropIdAreaId propIdAreaId{
                 .propId = value.prop,
                 .areaId = value.areaId,
@@ -264,7 +263,7 @@
         }
 
         for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
-            clients[client].push_back(&value);
+            clients[client].push_back(value);
         }
     }
     return clients;
diff --git a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
index fb14373..e775612 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
@@ -211,8 +211,9 @@
 
 class DefaultVehicleHalTest : public testing::Test {
   public:
-    void SetUp() override {
-        auto hardware = std::make_unique<MockVehicleHardware>();
+    void SetUp() override { init(std::make_unique<MockVehicleHardware>()); }
+
+    void init(std::unique_ptr<MockVehicleHardware> hardware) {
         std::vector<VehiclePropConfig> testConfigs;
         for (size_t i = 0; i < 10000; i++) {
             testConfigs.push_back(VehiclePropConfig{
@@ -1711,6 +1712,93 @@
     ASSERT_THAT(vehiclePropErrors.payloads, UnorderedElementsAreArray(expectedResults));
 }
 
+TEST_F(DefaultVehicleHalTest, testBatchOnPropertyChangeEvents) {
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    hardware->setPropertyOnChangeEventBatchingWindow(std::chrono::milliseconds(10));
+    init(std::move(hardware));
+
+    std::vector<SubscribeOptions> options = {
+            {
+                    .propId = GLOBAL_ON_CHANGE_PROP,
+            },
+            {
+                    .propId = AREA_ON_CHANGE_PROP,
+                    // No areaIds means subscribing to all area IDs.
+                    .areaIds = {},
+            },
+    };
+
+    getClient()->subscribe(getCallbackClient(), options, 0);
+    VehiclePropValue testValue1 = {
+            .prop = GLOBAL_ON_CHANGE_PROP,
+            .value.int32Values = {0},
+    };
+    SetValueRequest request1 = {
+            .requestId = 1,
+            .value = testValue1,
+    };
+    SetValueResult result1 = {
+            .requestId = 1,
+            .status = StatusCode::OK,
+    };
+    VehiclePropValue testValue2 = {
+            .prop = AREA_ON_CHANGE_PROP,
+            .areaId = toInt(VehicleAreaWindow::ROW_1_LEFT),
+            .value.int32Values = {1},
+    };
+    SetValueRequest request2 = {
+            .requestId = 2,
+            .value = testValue2,
+    };
+    SetValueResult result2 = {
+            .requestId = 2,
+            .status = StatusCode::OK,
+    };
+    VehiclePropValue testValue3 = {
+            .prop = AREA_ON_CHANGE_PROP,
+            .areaId = toInt(VehicleAreaWindow::ROW_1_RIGHT),
+            .value.int32Values = {1},
+    };
+    SetValueRequest request3 = {
+            .requestId = 3,
+            .value = testValue3,
+    };
+    SetValueResult result3 = {
+            .requestId = 3,
+            .status = StatusCode::OK,
+    };
+    // Prepare the responses
+    for (int i = 0; i < 2; i++) {
+        getHardware()->addSetValueResponses({result1});
+        getHardware()->addSetValueResponses({result2, result3});
+    }
+
+    // Try to cause two batches, each with three on property change events.
+    // Set GLOBAL_ON_CHANGE_PROP causing one event.
+    // Set AREA_ON_CHANGE_PROP with two areas causing two events.
+    for (int i = 0; i < 2; i++) {
+        auto status = getClient()->setValues(getCallbackClient(),
+                                             SetValueRequests{.payloads = {request1}});
+        ASSERT_TRUE(status.isOk()) << "setValues failed: " << status.getMessage();
+
+        status = getClient()->setValues(getCallbackClient(),
+                                        SetValueRequests{.payloads = {request2, request3}});
+        ASSERT_TRUE(status.isOk()) << "setValues failed: " << status.getMessage();
+
+        ASSERT_TRUE(getCallback()->waitForOnPropertyEventResults(/*size=*/1,
+                                                                 /*timeoutInNano=*/1'000'000'000))
+                << "not received enough property change events before timeout";
+
+        auto maybeResults = getCallback()->nextOnPropertyEventResults();
+        ASSERT_TRUE(maybeResults.has_value()) << "no results in callback";
+        ASSERT_THAT(maybeResults.value().payloads,
+                    UnorderedElementsAre(testValue1, testValue2, testValue3))
+                << "results mismatch, expect 3 batched on change events";
+        ASSERT_FALSE(getCallback()->nextOnPropertyEventResults().has_value())
+                << "more results than expected";
+    }
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
index 54fede1..c272123 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
@@ -137,6 +137,14 @@
     });
 }
 
+bool MockVehicleCallback::waitForOnPropertyEventResults(size_t size, size_t timeoutInNano) {
+    std::unique_lock lk(mLock);
+    return mCond.wait_for(lk, std::chrono::nanoseconds(timeoutInNano), [this, size] {
+        ScopedLockAssertion lockAssertion(mLock);
+        return mOnPropertyEventResults.size() >= size;
+    });
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
index 1545eae..672ff4f 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
@@ -69,6 +69,7 @@
     size_t countOnPropertyEventResults();
     bool waitForSetValueResults(size_t size, size_t timeoutInNano);
     bool waitForGetValueResults(size_t size, size_t timeoutInNano);
+    bool waitForOnPropertyEventResults(size_t size, size_t timeoutInNano);
 
   private:
     std::mutex mLock;
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
index ba0d33d..3fae596 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
@@ -186,6 +186,16 @@
     mSleepTime = timeInNano;
 }
 
+void MockVehicleHardware::setPropertyOnChangeEventBatchingWindow(std::chrono::nanoseconds window) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    mEventBatchingWindow = window;
+}
+
+std::chrono::nanoseconds MockVehicleHardware::getPropertyOnChangeEventBatchingWindow() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    return mEventBatchingWindow;
+}
+
 template <class ResultType>
 StatusCode MockVehicleHardware::returnResponse(
         std::shared_ptr<const std::function<void(std::vector<ResultType>)>> callback,
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
index 46b30b9..3ce18c5 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
@@ -24,6 +24,7 @@
 #include <android-base/thread_annotations.h>
 
 #include <atomic>
+#include <chrono>
 #include <condition_variable>
 #include <list>
 #include <memory>
@@ -60,6 +61,7 @@
     void registerOnPropertySetErrorEvent(std::unique_ptr<const PropertySetErrorCallback>) override;
     aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
             int32_t propId, int32_t areaId, float sampleRate) override;
+    std::chrono::nanoseconds getPropertyOnChangeEventBatchingWindow() override;
 
     // Test functions.
     void setPropertyConfigs(
@@ -86,6 +88,7 @@
     void setSleepTime(int64_t timeInNano);
     void setDumpResult(DumpResult result);
     void sendOnPropertySetErrorEvent(const std::vector<SetValueErrorEvent>& errorEvents);
+    void setPropertyOnChangeEventBatchingWindow(std::chrono::nanoseconds window);
 
   private:
     mutable std::mutex mLock;
@@ -110,6 +113,7 @@
             std::shared_ptr<const GetValuesCallback>,
             const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&)>
             mGetValueResponder GUARDED_BY(mLock);
+    std::chrono::nanoseconds mEventBatchingWindow GUARDED_BY(mLock) = std::chrono::nanoseconds(0);
 
     template <class ResultType>
     aidl::android::hardware::automotive::vehicle::StatusCode returnResponse(
diff --git a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
index cb8c8d1..5464304 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
@@ -48,7 +48,7 @@
 using ::ndk::ScopedAStatus;
 using ::ndk::SpAIBinder;
 using ::testing::ElementsAre;
-using ::testing::WhenSorted;
+using ::testing::UnorderedElementsAre;
 
 class PropertyCallback final : public BnVehicleCallback {
   public:
@@ -389,11 +389,11 @@
                     .areaId = 1,
             },
     };
-    auto clients = getManager()->getSubscribedClients(updatedValues);
+    auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
 
     ASSERT_THAT(clients[client1],
-                WhenSorted(ElementsAre(&updatedValues[0], &updatedValues[1], &updatedValues[2])));
-    ASSERT_THAT(clients[client2], ElementsAre(&updatedValues[0]));
+                UnorderedElementsAre(updatedValues[0], updatedValues[1], updatedValues[2]));
+    ASSERT_THAT(clients[client2], ElementsAre(updatedValues[0]));
 }
 
 TEST_F(SubscriptionManagerTest, testSubscribeInvalidOption) {
@@ -480,9 +480,9 @@
                     .areaId = 0,
             },
     };
-    auto clients = getManager()->getSubscribedClients(updatedValues);
+    auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
 
-    ASSERT_THAT(clients[getCallbackClient()], ElementsAre(&updatedValues[1]));
+    ASSERT_THAT(clients[getCallbackClient()], ElementsAre(updatedValues[1]));
 }
 
 TEST_F(SubscriptionManagerTest, testCheckSampleRateHzValid) {
diff --git a/broadcastradio/aidl/default/Android.bp b/broadcastradio/aidl/default/Android.bp
index 1d1bef7..e95208c 100644
--- a/broadcastradio/aidl/default/Android.bp
+++ b/broadcastradio/aidl/default/Android.bp
@@ -62,7 +62,7 @@
 
 cc_library {
     name: "DefaultBroadcastRadioHal",
-    vendor: true,
+    vendor_available: true,
     export_include_dirs: ["."],
     defaults: [
         "BroadcastRadioHalDefaults",
@@ -76,7 +76,8 @@
 
 cc_fuzz {
     name: "android.hardware.broadcastradio-service.default_fuzzer",
-    vendor: true,
+    // TODO(b/307611931): avoid fuzzing on vendor until hermiticity issue is fixed
+    // vendor: true,
     defaults: [
         "BroadcastRadioHalDefaults",
         "service_fuzzer_defaults",
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
index 4409314..7f9b6d6 100644
--- a/rebootescrow/aidl/default/Android.bp
+++ b/rebootescrow/aidl/default/Android.bp
@@ -42,10 +42,10 @@
 
 cc_binary {
     name: "android.hardware.rebootescrow-service.default",
-    init_rc: ["rebootescrow-default.rc"],
     relative_install_path: "hw",
-    vintf_fragments: ["rebootescrow-default.xml"],
     vendor: true,
+    installable: false, // installed in APEX
+
     srcs: [
         "service.cpp",
     ],
@@ -53,12 +53,14 @@
         "-Wall",
         "-Werror",
     ],
+    stl: "c++_static",
     shared_libs: [
-        "libbase",
         "libbinder_ndk",
-        "android.hardware.rebootescrow-V1-ndk",
+        "liblog",
     ],
     static_libs: [
+        "android.hardware.rebootescrow-V1-ndk",
+        "libbase",
         "libhadamardutils",
         "librebootescrowdefaultimpl",
     ],
@@ -97,3 +99,35 @@
     ],
     test_suites: ["device-tests"],
 }
+
+prebuilt_etc {
+    name: "rebootescrow-default.rc",
+    src: "rebootescrow-default.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "rebootescrow-default.xml",
+    src: "rebootescrow-default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.rebootescrow",
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    vendor: true,
+    updatable: false,
+
+    binaries: [
+        "android.hardware.rebootescrow-service.default",
+    ],
+    prebuilts: [
+        "rebootescrow-default.rc",
+        "rebootescrow-default.xml",
+        "android.hardware.reboot_escrow.prebuilt.xml", // <feature>
+    ],
+}
diff --git a/rebootescrow/aidl/default/apex_file_contexts b/rebootescrow/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..aa84984
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                      u:object_r:vendor_file:s0
+/etc(/.*)?                                                  u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.rebootescrow-service\.default    u:object_r:hal_rebootescrow_default_exec:s0
diff --git a/rebootescrow/aidl/default/apex_manifest.json b/rebootescrow/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..8be495b
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.rebootescrow",
+    "version": 1
+}
\ No newline at end of file
diff --git a/rebootescrow/aidl/default/rebootescrow-default.rc b/rebootescrow/aidl/default/rebootescrow-default.rc
index ad90465..024dd6d 100644
--- a/rebootescrow/aidl/default/rebootescrow-default.rc
+++ b/rebootescrow/aidl/default/rebootescrow-default.rc
@@ -1,4 +1,4 @@
-service vendor.rebootescrow-default /vendor/bin/hw/android.hardware.rebootescrow-service.default
+service vendor.rebootescrow-default /apex/com.android.hardware.rebootescrow/bin/hw/android.hardware.rebootescrow-service.default
     interface aidl android.hardware.rebootescrow.IRebootEscrow/default
     class hal
     user system
diff --git a/secure_element/aidl/default/Android.bp b/secure_element/aidl/default/Android.bp
index d1bb393..b382822 100644
--- a/secure_element/aidl/default/Android.bp
+++ b/secure_element/aidl/default/Android.bp
@@ -11,14 +11,50 @@
     name: "android.hardware.secure_element-service.example",
     relative_install_path: "hw",
     vendor: true,
-    init_rc: ["secure_element.rc"],
-    vintf_fragments: ["secure_element.xml"],
+    installable: false, // installed in APEX
+
+    stl: "c++_static",
     shared_libs: [
-        "libbase",
         "libbinder_ndk",
+        "liblog",
+    ],
+    static_libs: [
         "android.hardware.secure_element-V1-ndk",
+        "libbase",
     ],
     srcs: [
         "main.cpp",
     ],
 }
+
+prebuilt_etc {
+    name: "secure_element.rc",
+    src: "secure_element.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "secure_element.xml",
+    src: "secure_element.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.secure_element",
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    vendor: true,
+    updatable: false,
+
+    binaries: [
+        "android.hardware.secure_element-service.example",
+    ],
+    prebuilts: [
+        "secure_element.rc",
+        "secure_element.xml",
+        "android.hardware.se.omapi.ese.prebuilt.xml", // <feature>
+    ],
+}
diff --git a/secure_element/aidl/default/apex_file_contexts b/secure_element/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..e9e811e
--- /dev/null
+++ b/secure_element/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                      u:object_r:vendor_file:s0
+/etc(/.*)?                                                  u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.secure_element-service\.example  u:object_r:hal_secure_element_default_exec:s0
\ No newline at end of file
diff --git a/secure_element/aidl/default/apex_manifest.json b/secure_element/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..6e04c11
--- /dev/null
+++ b/secure_element/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.secure_element",
+    "version": 1
+}
\ No newline at end of file
diff --git a/secure_element/aidl/default/secure_element.rc b/secure_element/aidl/default/secure_element.rc
index 7d21666..b74b2ee 100644
--- a/secure_element/aidl/default/secure_element.rc
+++ b/secure_element/aidl/default/secure_element.rc
@@ -1,4 +1,4 @@
-service vendor.secure_element /vendor/bin/hw/android.hardware.secure_element-service.example
+service vendor.secure_element /apex/com.android.hardware.secure_element/bin/hw/android.hardware.secure_element-service.example
     class hal
     user nobody
     group nobody