Merge "Moved emulated User HAL capabilities into a library." into rvc-dev
diff --git a/automotive/vehicle/2.0/default/Android.bp b/automotive/vehicle/2.0/default/Android.bp
index 21b410a..872b35b 100644
--- a/automotive/vehicle/2.0/default/Android.bp
+++ b/automotive/vehicle/2.0/default/Android.bp
@@ -75,7 +75,10 @@
     ],
     local_include_dirs: ["common/include/vhal_v2_0"],
     export_include_dirs: ["impl"],
-    whole_static_libs: ["android.hardware.automotive.vehicle@2.0-manager-lib"],
+    whole_static_libs: [
+        "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib",
+        "android.hardware.automotive.vehicle@2.0-manager-lib",
+    ],
     shared_libs: [
         "libbase",
         "libjsoncpp",
@@ -87,6 +90,16 @@
     ],
 }
 
+// Library used  to emulate User HAL behavior through lshal debug requests.
+cc_library_static {
+    name: "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib",
+    vendor: true,
+    defaults: ["vhal_v2_0_defaults"],
+    srcs: [
+        "impl/vhal_v2_0/EmulatedUserHal.cpp",
+    ],
+}
+
 cc_test {
     name: "android.hardware.automotive.vehicle@2.0-manager-unit-tests",
     vendor: true,
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
index 83546e2..b8a606a 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h
@@ -79,8 +79,6 @@
 constexpr int WHEEL_FRONT_RIGHT = (int)VehicleAreaWheel::RIGHT_FRONT;
 constexpr int WHEEL_REAR_LEFT = (int)VehicleAreaWheel::LEFT_REAR;
 constexpr int WHEEL_REAR_RIGHT = (int)VehicleAreaWheel::RIGHT_REAR;
-constexpr int INITIAL_USER_INFO = (int)VehicleProperty::INITIAL_USER_INFO;
-constexpr int SWITCH_USER = (int)VehicleProperty::SWITCH_USER;
 
 /**
  * This property is used for test purpose to generate fake events. Here is the test package that
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
new file mode 100644
index 0000000..c49fadc
--- /dev/null
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "EmulatedUserHal"
+
+#include <cutils/log.h>
+#include <utils/SystemClock.h>
+
+#include "EmulatedUserHal.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+namespace impl {
+
+constexpr int INITIAL_USER_INFO = static_cast<int>(VehicleProperty::INITIAL_USER_INFO);
+constexpr int SWITCH_USER = static_cast<int>(VehicleProperty::SWITCH_USER);
+
+bool EmulatedUserHal::isSupported(int32_t prop) {
+    switch (prop) {
+        case INITIAL_USER_INFO:
+        case SWITCH_USER:
+            return true;
+        default:
+            return false;
+    }
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::onSetProperty(
+        const VehiclePropValue& value) {
+    ALOGV("onSetProperty(): %s", toString(value).c_str());
+
+    switch (value.prop) {
+        case INITIAL_USER_INFO:
+            return onSetInitialUserInfoResponse(value);
+        case SWITCH_USER:
+            return onSetSwitchUserResponse(value);
+        default:
+            return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+                   << "Unsupported property: " << toString(value);
+    }
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>>
+EmulatedUserHal::onSetInitialUserInfoResponse(const VehiclePropValue& value) {
+    if (value.value.int32Values.size() == 0) {
+        ALOGE("set(INITIAL_USER_INFO): no int32values, ignoring it: %s", toString(value).c_str());
+        return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+               << "no int32values on " << toString(value);
+    }
+
+    if (value.areaId != 0) {
+        ALOGD("set(INITIAL_USER_INFO) called from lshal; storing it: %s", toString(value).c_str());
+        mInitialUserResponseFromCmd.reset(new VehiclePropValue(value));
+        return {};
+    }
+
+    ALOGD("set(INITIAL_USER_INFO) called from Android: %s", toString(value).c_str());
+
+    int32_t requestId = value.value.int32Values[0];
+    if (mInitialUserResponseFromCmd != nullptr) {
+        ALOGI("replying INITIAL_USER_INFO with lshal value:  %s",
+              toString(*mInitialUserResponseFromCmd).c_str());
+        return sendUserHalResponse(std::move(mInitialUserResponseFromCmd), requestId);
+    }
+
+    // Returns default response
+    auto updatedValue = std::unique_ptr<VehiclePropValue>(new VehiclePropValue);
+    updatedValue->prop = INITIAL_USER_INFO;
+    updatedValue->timestamp = elapsedRealtimeNano();
+    updatedValue->value.int32Values.resize(2);
+    updatedValue->value.int32Values[0] = requestId;
+    updatedValue->value.int32Values[1] = (int32_t)InitialUserInfoResponseAction::DEFAULT;
+
+    ALOGI("no lshal response; replying with InitialUserInfoResponseAction::DEFAULT: %s",
+          toString(*updatedValue).c_str());
+
+    return updatedValue;
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::onSetSwitchUserResponse(
+        const VehiclePropValue& value) {
+    if (value.value.int32Values.size() == 0) {
+        ALOGE("set(SWITCH_USER): no int32values, ignoring it: %s", toString(value).c_str());
+        return android::base::Error(static_cast<int>(StatusCode::INVALID_ARG))
+               << "no int32values on " << toString(value);
+    }
+
+    if (value.areaId != 0) {
+        ALOGD("set(SWITCH_USER) called from lshal; storing it: %s", toString(value).c_str());
+        mSwitchUserResponseFromCmd.reset(new VehiclePropValue(value));
+        return {};
+    }
+    ALOGD("set(SWITCH_USER) called from Android: %s", toString(value).c_str());
+
+    int32_t requestId = value.value.int32Values[0];
+    if (mSwitchUserResponseFromCmd != nullptr) {
+        ALOGI("replying SWITCH_USER with lshal value:  %s",
+              toString(*mSwitchUserResponseFromCmd).c_str());
+        return sendUserHalResponse(std::move(mSwitchUserResponseFromCmd), requestId);
+    }
+
+    // Returns default response
+    auto updatedValue = std::unique_ptr<VehiclePropValue>(new VehiclePropValue);
+    updatedValue->prop = SWITCH_USER;
+    updatedValue->timestamp = elapsedRealtimeNano();
+    updatedValue->value.int32Values.resize(3);
+    updatedValue->value.int32Values[0] = requestId;
+    updatedValue->value.int32Values[1] = (int32_t)SwitchUserMessageType::VEHICLE_RESPONSE;
+    updatedValue->value.int32Values[2] = (int32_t)SwitchUserStatus::SUCCESS;
+
+    ALOGI("no lshal response; replying with VEHICLE_RESPONSE / SUCCESS: %s",
+          toString(*updatedValue).c_str());
+
+    return updatedValue;
+}
+
+android::base::Result<std::unique_ptr<VehiclePropValue>> EmulatedUserHal::sendUserHalResponse(
+        std::unique_ptr<VehiclePropValue> response, int32_t requestId) {
+    switch (response->areaId) {
+        case 1:
+            ALOGD("returning response with right request id");
+            response->value.int32Values[0] = requestId;
+            break;
+        case 2:
+            ALOGD("returning response with wrong request id");
+            response->value.int32Values[0] = -requestId;
+            break;
+        case 3:
+            ALOGD("not generating a property change event because of lshal prop: %s",
+                  toString(*response).c_str());
+            return android::base::Error(static_cast<int>(StatusCode::NOT_AVAILABLE))
+                   << "not generating a property change event because of lshal prop: "
+                   << toString(*response);
+        default:
+            ALOGE("invalid action on lshal response: %s", toString(*response).c_str());
+            return android::base::Error(static_cast<int>(StatusCode::INTERNAL_ERROR))
+                   << "invalid action on lshal response: " << toString(*response);
+    }
+
+    ALOGD("updating property to: %s", toString(*response).c_str());
+
+    return response;
+}
+
+void EmulatedUserHal::showDumpHelp(int fd) {
+    dprintf(fd, "%s: dumps state used for user management\n", kUserHalDumpOption);
+}
+
+void EmulatedUserHal::dump(int fd, std::string indent) {
+    if (mInitialUserResponseFromCmd != nullptr) {
+        dprintf(fd, "%sInitialUserInfo response: %s\n", indent.c_str(),
+                toString(*mInitialUserResponseFromCmd).c_str());
+    } else {
+        dprintf(fd, "%sNo InitialUserInfo response\n", indent.c_str());
+    }
+    if (mSwitchUserResponseFromCmd != nullptr) {
+        dprintf(fd, "%sSwitchUser response: %s\n", indent.c_str(),
+                toString(*mSwitchUserResponseFromCmd).c_str());
+    } else {
+        dprintf(fd, "%sNo SwitchUser response\n", indent.c_str());
+    }
+}
+
+}  // namespace impl
+
+}  // namespace V2_0
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h
new file mode 100644
index 0000000..b25efcb
--- /dev/null
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
+#define android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
+
+#include <android-base/result.h>
+
+#include <android/hardware/automotive/vehicle/2.0/types.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace vehicle {
+namespace V2_0 {
+
+namespace impl {
+
+constexpr char kUserHalDumpOption[] = "--user-hal";
+
+/**
+ * Class used to emulate User HAL behavior through lshal debug requests.
+ */
+class EmulatedUserHal {
+  public:
+    EmulatedUserHal() {}
+
+    ~EmulatedUserHal() = default;
+
+    /**
+     * Checks if the emulator can handle the property.
+     */
+    bool isSupported(int32_t prop);
+
+    /**
+     * Lets the emulator handle the property.
+     *
+     * @return updated property and StatusCode
+     */
+    android::base::Result<std::unique_ptr<VehiclePropValue>> onSetProperty(
+            const VehiclePropValue& value);
+
+    /**
+     * Shows the User HAL emulation help.
+     */
+    void showDumpHelp(int fd);
+
+    /**
+     * Dump its contents.
+     */
+    void dump(int fd, std::string indent);
+
+  private:
+    /**
+     * INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change
+     * indicating what the initial user should be.
+     *
+     * During normal circumstances, the emulator will reply right away, passing a response if
+     * InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which
+     * user to boot).
+     *
+     * But during development / testing, the behavior can be changed using lshal dump, which must
+     * use the areaId to indicate what should happen next.
+     *
+     * So, the behavior of set(INITIAL_USER_INFO) is:
+     *
+     * - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called
+     * by lshal).
+     * - else if mInitialUserResponseFromCmd is not set, return a response with the same request id
+     * and InitialUserInfoResponseAction::DEFAULT
+     * - else the behavior is defined by the areaId on mInitialUserResponseFromCmd:
+     * - if it's 1, reply with mInitialUserResponseFromCmd and the right request id
+     * - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can
+     * test this error scenario)
+     * - if it's 3, then don't send a property change (so Android can emulate a timeout)
+     *
+     */
+    android::base::Result<std::unique_ptr<VehiclePropValue>> onSetInitialUserInfoResponse(
+            const VehiclePropValue& value);
+
+    /**
+     * Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage.
+     */
+    android::base::Result<std::unique_ptr<VehiclePropValue>> onSetSwitchUserResponse(
+            const VehiclePropValue& value);
+
+    android::base::Result<std::unique_ptr<VehiclePropValue>> sendUserHalResponse(
+            std::unique_ptr<VehiclePropValue> response, int32_t requestId);
+
+    std::unique_ptr<VehiclePropValue> mInitialUserResponseFromCmd;
+    std::unique_ptr<VehiclePropValue> mSwitchUserResponseFromCmd;
+};
+
+}  // namespace impl
+
+}  // namespace V2_0
+}  // namespace vehicle
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
index ce7dc65..7f9362f 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp
@@ -38,9 +38,6 @@
 class EmulatedPassthroughConnector : public PassthroughConnector {
   public:
     bool onDump(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
-
-  private:
-    void dumpUserHal(int fd, std::string indent);
 };
 
 bool EmulatedPassthroughConnector::onDump(const hidl_handle& handle,
@@ -50,12 +47,12 @@
     if (options.size() > 0) {
         if (options[0] == "--help") {
             dprintf(fd, "Emulator-specific usage:\n");
-            dprintf(fd, "--user-hal: dumps state used for user management \n");
+            mEmulatedUserHal.showDumpHelp(fd);
             dprintf(fd, "\n");
             // Include caller's help options
             return true;
-        } else if (options[0] == "--user-hal") {
-            dumpUserHal(fd, "");
+        } else if (options[0] == kUserHalDumpOption) {
+            mEmulatedUserHal.dump(fd, "");
             return false;
 
         } else {
@@ -65,27 +62,12 @@
     }
 
     dprintf(fd, "Emulator-specific state:\n");
-    dumpUserHal(fd, "  ");
+    mEmulatedUserHal.dump(fd, "  ");
     dprintf(fd, "\n");
 
     return true;
 }
 
-void EmulatedPassthroughConnector::dumpUserHal(int fd, std::string indent) {
-    if (mInitialUserResponseFromCmd != nullptr) {
-        dprintf(fd, "%sInitialUserInfo response: %s\n", indent.c_str(),
-                toString(*mInitialUserResponseFromCmd).c_str());
-    } else {
-        dprintf(fd, "%sNo InitialUserInfo response\n", indent.c_str());
-    }
-    if (mSwitchUserResponseFromCmd != nullptr) {
-        dprintf(fd, "%sSwitchUser response: %s\n", indent.c_str(),
-                toString(*mSwitchUserResponseFromCmd).c_str());
-    } else {
-        dprintf(fd, "%sNo SwitchUser response\n", indent.c_str());
-    }
-}
-
 PassthroughConnectorPtr makeEmulatedPassthroughConnector() {
     return std::make_unique<EmulatedPassthroughConnector>();
 }
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
index 70e39eb..ad5096e 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp
@@ -181,6 +181,23 @@
 }
 
 StatusCode VehicleHalServer::onSetProperty(const VehiclePropValue& value, bool updateStatus) {
+    if (mEmulatedUserHal.isSupported(value.prop)) {
+        LOG(INFO) << "onSetProperty(): property " << value.prop << " will be handled by UserHal";
+
+        const auto& ret = mEmulatedUserHal.onSetProperty(value);
+        if (!ret.ok()) {
+            LOG(ERROR) << "onSetProperty(): HAL returned error: " << ret.error().message();
+            return StatusCode(ret.error().code());
+        }
+        auto updatedValue = ret.value().get();
+        if (updatedValue != nullptr) {
+            LOG(INFO) << "onSetProperty(): updating property returned by HAL: "
+                      << toString(*updatedValue);
+            onPropertyValueFromCar(*updatedValue, updateStatus);
+        }
+        return StatusCode::OK;
+    }
+
     // Some properties need to be treated non-trivially
     switch (value.prop) {
         case kGenerateFakeDataControllingProperty:
@@ -245,10 +262,6 @@
                     break;
             }
             break;
-        case INITIAL_USER_INFO:
-            return onSetInitialUserInfoResponse(value, updateStatus);
-        case SWITCH_USER:
-            return onSetSwitchUserResponse(value, updateStatus);
         default:
             break;
     }
@@ -262,165 +275,4 @@
     return StatusCode::OK;
 }
 
-/**
- * INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change
- * indicating what the initial user should be.
- *
- * During normal circumstances, the emulator will reply right away, passing a response if
- * InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which user
- * to boot).
- *
- * But during development / testing, the behavior can be changed using lshal dump, which must use
- * the areaId to indicate what should happen next.
- *
- * So, the behavior of set(INITIAL_USER_INFO) is:
- *
- * - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called by
- *   lshal).
- * - else if mInitialUserResponseFromCmd is not set, return a response with the same request id and
- *   InitialUserInfoResponseAction::DEFAULT
- * - else the behavior is defined by the areaId on mInitialUserResponseFromCmd:
- * - if it's 1, reply with mInitialUserResponseFromCmd and the right request id
- * - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can test
- *   this error scenario)
- * - if it's 3, then don't send a property change (so Android can emulate a timeout)
- *
- */
-StatusCode VehicleHalServer::onSetInitialUserInfoResponse(const VehiclePropValue& value,
-                                                          bool updateStatus) {
-    // TODO: LOG calls below might be more suited to be DEBUG, but those are not being logged
-    // (even when explicitly calling setprop log.tag. As this class should be using ALOG instead of
-    // LOG, it's not worth investigating why...
-
-    if (value.value.int32Values.size() == 0) {
-        LOG(ERROR) << "set(INITIAL_USER_INFO): no int32values, ignoring it: " << toString(value);
-        return StatusCode::INVALID_ARG;
-    }
-
-    if (value.areaId != 0) {
-        LOG(INFO) << "set(INITIAL_USER_INFO) called from lshal; storing it: " << toString(value);
-        mInitialUserResponseFromCmd.reset(new VehiclePropValue(value));
-        return StatusCode::OK;
-    }
-    LOG(INFO) << "set(INITIAL_USER_INFO) called from Android: " << toString(value);
-
-    int32_t requestId = value.value.int32Values[0];
-
-    // Create the update property and set common values
-    auto updatedValue = createVehiclePropValue(VehiclePropertyType::MIXED, 0);
-    updatedValue->prop = INITIAL_USER_INFO;
-    updatedValue->timestamp = elapsedRealtimeNano();
-
-    if (mInitialUserResponseFromCmd == nullptr) {
-        updatedValue->value.int32Values.resize(2);
-        updatedValue->value.int32Values[0] = requestId;
-        updatedValue->value.int32Values[1] = (int32_t)InitialUserInfoResponseAction::DEFAULT;
-        LOG(INFO) << "no lshal response; returning InitialUserInfoResponseAction::DEFAULT: "
-                  << toString(*updatedValue);
-        onPropertyValueFromCar(*updatedValue, updateStatus);
-        return StatusCode::OK;
-    }
-
-    // mInitialUserResponseFromCmd is used for just one request
-    std::unique_ptr<VehiclePropValue> response = std::move(mInitialUserResponseFromCmd);
-
-    // TODO(b/150409377): rather than populate the raw values directly, it should use the
-    // libraries that convert a InitialUserInfoResponse into a VehiclePropValue)
-
-    switch (response->areaId) {
-        case 1:
-            LOG(INFO) << "returning response with right request id";
-            *updatedValue = *response;
-            updatedValue->areaId = 0;
-            updatedValue->value.int32Values[0] = requestId;
-            break;
-        case 2:
-            LOG(INFO) << "returning response with wrong request id";
-            *updatedValue = *response;
-            updatedValue->areaId = 0;
-            updatedValue->value.int32Values[0] = -requestId;
-            break;
-        case 3:
-            LOG(INFO) << "not generating a property change event because of lshal prop: "
-                      << toString(*response);
-            return StatusCode::OK;
-        default:
-            LOG(ERROR) << "invalid action on lshal response: " << toString(*response);
-            return StatusCode::INTERNAL_ERROR;
-    }
-
-    LOG(INFO) << "updating property to: " << toString(*updatedValue);
-    onPropertyValueFromCar(*updatedValue, updateStatus);
-    return StatusCode::OK;
-}
-
-/**
- * Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage.
- */
-StatusCode VehicleHalServer::onSetSwitchUserResponse(const VehiclePropValue& value,
-                                                     bool updateStatus) {
-    if (value.value.int32Values.size() == 0) {
-        LOG(ERROR) << "set(SWITCH_USER): no int32values, ignoring it: " << toString(value);
-        return StatusCode::INVALID_ARG;
-    }
-
-    if (value.areaId != 0) {
-        LOG(INFO) << "set(SWITCH_USER) called from lshal; storing it: " << toString(value);
-        mSwitchUserResponseFromCmd.reset(new VehiclePropValue(value));
-        return StatusCode::OK;
-    }
-    LOG(INFO) << "set(SWITCH_USER) called from Android: " << toString(value);
-
-    int32_t requestId = value.value.int32Values[0];
-
-    // Create the update property and set common values
-    auto updatedValue = createVehiclePropValue(VehiclePropertyType::MIXED, 0);
-    updatedValue->prop = SWITCH_USER;
-    updatedValue->timestamp = elapsedRealtimeNano();
-
-    if (mSwitchUserResponseFromCmd == nullptr) {
-        updatedValue->value.int32Values.resize(3);
-        updatedValue->value.int32Values[0] = requestId;
-        updatedValue->value.int32Values[1] = (int32_t)SwitchUserMessageType::VEHICLE_RESPONSE;
-        updatedValue->value.int32Values[2] = (int32_t)SwitchUserStatus::SUCCESS;
-        LOG(INFO) << "no lshal response; returning VEHICLE_RESPONSE / SUCCESS: "
-                  << toString(*updatedValue);
-        onPropertyValueFromCar(*updatedValue, updateStatus);
-        return StatusCode::OK;
-    }
-
-    // mSwitchUserResponseFromCmd is used for just one request
-    std::unique_ptr<VehiclePropValue> response = std::move(mSwitchUserResponseFromCmd);
-
-    // TODO(b/150409377): move code below to a local function like sendUserHalResponse(),
-    // as it's the same for all (like onSetInitialUserInfoResponse)
-
-    switch (response->areaId) {
-        case 1:
-            LOG(INFO) << "returning response with right request id";
-            *updatedValue = *response;
-            updatedValue->areaId = 0;
-            updatedValue->value.int32Values[0] = requestId;
-            break;
-        case 2:
-            LOG(INFO) << "returning response with wrong request id";
-            *updatedValue = *response;
-            updatedValue->areaId = 0;
-            updatedValue->value.int32Values[0] = -requestId;
-            break;
-        case 3:
-            LOG(INFO) << "not generating a property change event because of lshal prop: "
-                      << toString(*response);
-            return StatusCode::OK;
-        default:
-            LOG(ERROR) << "invalid action on lshal response: " << toString(*response);
-            return StatusCode::INTERNAL_ERROR;
-    }
-
-    LOG(INFO) << "updating property to: " << toString(*updatedValue);
-    onPropertyValueFromCar(*updatedValue, updateStatus);
-
-    return StatusCode::OK;
-}
-
 }  // namespace android::hardware::automotive::vehicle::V2_0::impl
diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
index 20e094a..2841fbe 100644
--- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
+++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h
@@ -19,6 +19,7 @@
 #include <vhal_v2_0/VehicleObjectPool.h>
 #include <vhal_v2_0/VehicleServer.h>
 
+#include "EmulatedUserHal.h"
 #include "GeneratorHub.h"
 
 namespace android::hardware::automotive::vehicle::V2_0::impl {
@@ -53,15 +54,10 @@
     VehiclePropValuePtr createHwInputKeyProp(VehicleHwKeyInputAction action, int32_t keyCode,
                                              int32_t targetDisplay);
 
-    StatusCode onSetInitialUserInfoResponse(const VehiclePropValue& value, bool updateStatus);
-    StatusCode onSetSwitchUserResponse(const VehiclePropValue& value, bool updateStatus);
-
     // data members
 
   protected:
-    // TODO(b/146207078): it might be clearer to move members below to an EmulatedUserHal class
-    std::unique_ptr<VehiclePropValue> mInitialUserResponseFromCmd;
-    std::unique_ptr<VehiclePropValue> mSwitchUserResponseFromCmd;
+    EmulatedUserHal mEmulatedUserHal;
 
   private:
     GeneratorHub mGeneratorHub{