diff --git a/automotive/vehicle/2.0/utils/Android.bp b/automotive/vehicle/2.0/utils/Android.bp new file mode 100644 index 0000000000..e354634165 --- /dev/null +++ b/automotive/vehicle/2.0/utils/Android.bp @@ -0,0 +1,41 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// User HAL helper library. +cc_library_static { + name: "android.hardware.automotive.vehicle@2.0-user-hal-helper-lib", + defaults: ["vhal_v2_0_defaults"], + vendor: true, + host_supported: true, + srcs: [ + "UserHalHelper.cpp", + ], + export_include_dirs: [ + ".", + ], +} + +cc_test { + name: "android.hardware.automotive.vehicle@2.0-utils-unit-tests", + defaults: ["vhal_v2_0_defaults"], + vendor: true, + srcs: [ + "tests/UserHalHelper_test.cpp", + ], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-user-hal-helper-lib", + "libgmock", + ], + test_suites: ["general-tests"], +} diff --git a/automotive/vehicle/2.0/utils/UserHalHelper.cpp b/automotive/vehicle/2.0/utils/UserHalHelper.cpp new file mode 100644 index 0000000000..33b39482da --- /dev/null +++ b/automotive/vehicle/2.0/utils/UserHalHelper.cpp @@ -0,0 +1,366 @@ +/* + * 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 "UserHalHelper" + +#include "UserHalHelper.h" + +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace user_hal_helper { +namespace { + +using android::base::Error; +using android::base::Result; + +static constexpr const char* kSeparator = "||"; +static const size_t kNumFieldsPerUserInfo = 2; +static const size_t kNumFieldsPerSetAssociation = 2; + +template +Result verifyAndCast(int32_t value) { + T castValue = static_cast(value); + const auto iter = hidl_enum_range(); + if (castValue < *iter.begin() || castValue > *std::prev(iter.end())) { + return Error() << "Value " << value << " not in range [" << toString(*iter.begin()) << ", " + << toString(*std::prev(iter.end())) << "]"; + } + for (const auto& v : hidl_enum_range()) { + if (castValue == v) { + return castValue; + } + } + return Error() << "Value " << value << " not in enum values"; +} + +Result verifyPropValue(const VehiclePropValue& propValue, VehicleProperty vehicleProperty, + size_t minInt32Values) { + auto prop = verifyAndCast(propValue.prop); + if (!prop.ok()) { + return Error() << "Invalid vehicle property: " << prop.error(); + } + if (*prop != vehicleProperty) { + return Error() << "Mismatching " << toString(vehicleProperty) << " request, received " + << toString(*prop) << " property"; + } + if (propValue.value.int32Values.size() < minInt32Values) { + return Error() << "Int32Values must have at least " << minInt32Values + << " values, received " << propValue.value.int32Values.size(); + } + return {}; +} + +Result parseUserInfo(const hidl_vec& int32Values, size_t startPos, + UserInfo* userInfo) { + if (int32Values.size() < startPos + kNumFieldsPerUserInfo) { + return Error() << "Int32Values must have at least " << startPos + 2 << " values, received " + << int32Values.size(); + } + userInfo->userId = int32Values[startPos]; + auto userFlags = verifyAndCast(int32Values[startPos + 1]); + if (!userFlags.ok()) { + return Error() << "Invalid user flags: " << userFlags.error(); + } + userInfo->flags = *userFlags; + return {}; +} + +Result parseUsersInfo(const hidl_vec& int32Values, size_t startPos, + UsersInfo* usersInfo) { + if (int32Values.size() < startPos + 3) { + return Error() << "Int32Values must have at least " << startPos + 3 << " values, received " + << int32Values.size(); + } + auto ret = parseUserInfo(int32Values, startPos, &usersInfo->currentUser); + if (!ret.ok()) { + return ret; + } + usersInfo->numberUsers = int32Values[startPos + 2]; + usersInfo->existingUsers.resize(usersInfo->numberUsers); + for (size_t i = 0; i < static_cast(usersInfo->numberUsers); ++i) { + ret = parseUserInfo(int32Values, startPos + 3 + (kNumFieldsPerUserInfo * i), + &usersInfo->existingUsers[i]); + if (!ret.ok()) { + return Error() << "Failed to parse existing user '" << i << "' info: " << ret.error(); + } + } + return {}; +} + +Result parseUserAssociationTypes( + const hidl_vec& int32Values, size_t startPos, size_t numberAssociationTypes, + hidl_vec* associationTypes) { + size_t minInt32Values = startPos + numberAssociationTypes; + if (int32Values.size() < minInt32Values) { + return Error() << "Int32Values must have at least " << minInt32Values + << " values, received " << int32Values.size(); + } + associationTypes->resize(numberAssociationTypes); + for (size_t i = 0; i < static_cast(numberAssociationTypes); ++i) { + size_t pos = startPos + i; + auto type = verifyAndCast(int32Values[pos]); + if (!type.ok()) { + return Error() << "Invalid association type in query '" << i << "': " << type.error(); + } + (*associationTypes)[i] = *type; + } + return {}; +} + +Result parseUserAssociations(const hidl_vec& int32Values, size_t startPos, + size_t numberAssociations, + hidl_vec* associations) { + size_t minInt32Values = startPos + (numberAssociations * kNumFieldsPerSetAssociation); + if (int32Values.size() < minInt32Values) { + return Error() << "Int32Values must have at least " << minInt32Values + << " values, received " << int32Values.size(); + } + associations->resize(numberAssociations); + for (size_t i = 0; i < static_cast(numberAssociations); ++i) { + size_t pos = startPos + (kNumFieldsPerSetAssociation * i); + auto type = verifyAndCast(int32Values[pos]); + if (!type.ok()) { + return Error() << "Invalid association type in request '" << i << "': " << type.error(); + } + (*associations)[i].type = *type; + auto value = verifyAndCast(int32Values[pos + 1]); + if (!value.ok()) { + return Error() << "Invalid association set value in request '" << i + << "': " << value.error(); + } + (*associations)[i].value = *value; + } + return {}; +} + +} // namespace + +Result toInitialUserInfoRequest(const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::INITIAL_USER_INFO, 2); + if (!ret.ok()) { + return ret.error(); + } + InitialUserInfoRequest request; + request.requestId = propValue.value.int32Values[0]; + auto requestType = verifyAndCast(propValue.value.int32Values[1]); + if (!requestType.ok()) { + return Error() << "Invalid InitialUserInfoRequestType: " << requestType.error(); + } + request.requestType = *requestType; + ret = parseUsersInfo(propValue.value.int32Values, 2, &request.usersInfo); + if (!ret.ok()) { + return Error() << "Failed to parse users info: " << ret.error(); + } + return request; +} + +Result toSwitchUserRequest(const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::SWITCH_USER, 2); + if (!ret.ok()) { + return ret.error(); + } + SwitchUserRequest request; + auto messageType = verifyAndCast(propValue.value.int32Values[1]); + if (!messageType.ok()) { + return Error() << "Invalid SwitchUserMessageType: " << messageType.error(); + } + if (*messageType != SwitchUserMessageType::LEGACY_ANDROID_SWITCH && + *messageType != SwitchUserMessageType::ANDROID_SWITCH && + *messageType != SwitchUserMessageType::ANDROID_POST_SWITCH) { + return Error() << "Invalid " << toString(*messageType) << " from Android System"; + } + request.requestId = propValue.value.int32Values[0]; + request.messageType = *messageType; + ret = parseUserInfo(propValue.value.int32Values, 2, &request.targetUser); + if (!ret.ok()) { + return Error() << "Failed to parse target user info: " << ret.error(); + } + ret = parseUsersInfo(propValue.value.int32Values, 4, &request.usersInfo); + if (!ret.ok()) { + return Error() << "Failed to parse users info: " << ret.error(); + } + return request; +} + +Result toCreateUserRequest(const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::CREATE_USER, 1); + if (!ret.ok()) { + return ret.error(); + } + CreateUserRequest request; + request.requestId = propValue.value.int32Values[0]; + ret = parseUserInfo(propValue.value.int32Values, 1, &request.newUserInfo); + if (!ret.ok()) { + return Error() << "Failed to parse new user info: " << ret.error(); + } + request.newUserName = propValue.value.stringValue; + ret = parseUsersInfo(propValue.value.int32Values, 3, &request.usersInfo); + if (!ret.ok()) { + return Error() << "Failed to parse users info: " << ret.error(); + } + return request; +} + +Result toRemoveUserRequest(const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::REMOVE_USER, 1); + if (!ret.ok()) { + return ret.error(); + } + RemoveUserRequest request; + request.requestId = propValue.value.int32Values[0]; + ret = parseUserInfo(propValue.value.int32Values, 1, &request.removedUserInfo); + if (!ret.ok()) { + return Error() << "Failed to parse removed user info: " << ret.error(); + } + ret = parseUsersInfo(propValue.value.int32Values, 3, &request.usersInfo); + if (!ret.ok()) { + return Error() << "Failed to parse users info: " << ret.error(); + } + return request; +} + +Result toUserIdentificationGetRequest( + const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::USER_IDENTIFICATION_ASSOCIATION, 4); + if (!ret.ok()) { + return ret.error(); + } + UserIdentificationGetRequest request; + request.requestId = propValue.value.int32Values[0]; + ret = parseUserInfo(propValue.value.int32Values, 1, &request.userInfo); + if (!ret.ok()) { + return Error() << "Failed to parse user info: " << ret.error(); + } + request.numberAssociationTypes = propValue.value.int32Values[3]; + ret = parseUserAssociationTypes(propValue.value.int32Values, 4, request.numberAssociationTypes, + &request.associationTypes); + if (!ret.ok()) { + return Error() << "Failed to parse UserIdentificationAssociationType: " << ret.error(); + } + return request; +} + +Result toUserIdentificationSetRequest( + const VehiclePropValue& propValue) { + auto ret = verifyPropValue(propValue, VehicleProperty::USER_IDENTIFICATION_ASSOCIATION, 4); + if (!ret.ok()) { + return ret.error(); + } + UserIdentificationSetRequest request; + request.requestId = propValue.value.int32Values[0]; + ret = parseUserInfo(propValue.value.int32Values, 1, &request.userInfo); + if (!ret.ok()) { + return Error() << "Failed to parse user info: " << ret.error(); + } + request.numberAssociations = propValue.value.int32Values[3]; + ret = parseUserAssociations(propValue.value.int32Values, 4, request.numberAssociations, + &request.associations); + if (!ret.ok()) { + return Error() << "Failed to parse UserIdentificationSetAssociation: " << ret.error(); + } + return request; +} + +std::unique_ptr toVehiclePropValue(const SwitchUserRequest& request) { + if (request.messageType != SwitchUserMessageType::VEHICLE_REQUEST) { + ALOGE("Invalid %s message type %s from HAL", toString(VehicleProperty::SWITCH_USER).c_str(), + toString(request.messageType).c_str()); + return nullptr; + } + auto propValue = std::unique_ptr(new VehiclePropValue()); + propValue->prop = static_cast(VehicleProperty::SWITCH_USER); + propValue->timestamp = elapsedRealtimeNano(); + propValue->value.int32Values.resize(3); + propValue->value.int32Values[0] = static_cast(request.requestId); + propValue->value.int32Values[1] = static_cast(request.messageType); + propValue->value.int32Values[2] = static_cast(request.targetUser.userId); + return propValue; +} + +std::unique_ptr toVehiclePropValue(const InitialUserInfoResponse& response) { + auto propValue = std::unique_ptr(new VehiclePropValue()); + propValue->prop = static_cast(VehicleProperty::INITIAL_USER_INFO); + propValue->timestamp = elapsedRealtimeNano(); + propValue->value.int32Values.resize(4); + propValue->value.int32Values[0] = static_cast(response.requestId); + propValue->value.int32Values[1] = static_cast(response.action); + propValue->value.int32Values[2] = static_cast(response.userToSwitchOrCreate.userId); + propValue->value.int32Values[3] = static_cast(response.userToSwitchOrCreate.flags); + propValue->value.stringValue = std::string(response.userLocales) + std::string(kSeparator) + + std::string(response.userNameToCreate); + return propValue; +} + +std::unique_ptr toVehiclePropValue(const SwitchUserResponse& response) { + auto propValue = std::unique_ptr(new VehiclePropValue()); + propValue->prop = static_cast(VehicleProperty::SWITCH_USER); + propValue->timestamp = elapsedRealtimeNano(); + propValue->value.int32Values.resize(3); + propValue->value.int32Values[0] = static_cast(response.requestId); + propValue->value.int32Values[1] = static_cast(response.messageType); + propValue->value.int32Values[2] = static_cast(response.status); + if (response.status == SwitchUserStatus::FAILURE) { + propValue->value.stringValue = response.errorMessage; + } + return propValue; +} + +std::unique_ptr toVehiclePropValue(const CreateUserResponse& response) { + auto propValue = std::unique_ptr(new VehiclePropValue()); + propValue->prop = static_cast(VehicleProperty::CREATE_USER); + propValue->timestamp = elapsedRealtimeNano(); + propValue->value.int32Values.resize(2); + propValue->value.int32Values[0] = static_cast(response.requestId); + propValue->value.int32Values[1] = static_cast(response.status); + if (response.status == CreateUserStatus::FAILURE) { + propValue->value.stringValue = response.errorMessage; + } + return propValue; +} + +std::unique_ptr toVehiclePropValue(const UserIdentificationResponse& response) { + auto propValue = std::unique_ptr(new VehiclePropValue()); + propValue->prop = static_cast(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION); + propValue->timestamp = elapsedRealtimeNano(); + propValue->value.int32Values.resize(2 + (response.numberAssociation * 2)); + propValue->value.int32Values[0] = static_cast(response.requestId); + propValue->value.int32Values[1] = static_cast(response.numberAssociation); + for (size_t i = 0; i < static_cast(response.numberAssociation); ++i) { + size_t int32ValuesPos = 2 + (2 * i); + propValue->value.int32Values[int32ValuesPos] = + static_cast(response.associations[i].type); + propValue->value.int32Values[int32ValuesPos + 1] = + static_cast(response.associations[i].value); + } + if (!response.errorMessage.empty()) { + propValue->value.stringValue = response.errorMessage; + } + return propValue; +} + +} // namespace user_hal_helper + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/utils/UserHalHelper.h b/automotive/vehicle/2.0/utils/UserHalHelper.h new file mode 100644 index 0000000000..bee34cf0f6 --- /dev/null +++ b/automotive/vehicle/2.0/utils/UserHalHelper.h @@ -0,0 +1,62 @@ +/* + * 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_UserHalHelper_H_ +#define android_hardware_automotive_vehicle_V2_0_impl_UserHalHelper_H_ + +#include +#include + +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace user_hal_helper { + +// Below functions parse VehiclePropValues to the respective User HAL request structs. On success, +// these functions return the User HAL struct. Otherwise, they return the error. +android::base::Result toInitialUserInfoRequest( + const VehiclePropValue& propValue); +android::base::Result toSwitchUserRequest(const VehiclePropValue& propValue); +android::base::Result toCreateUserRequest(const VehiclePropValue& propValue); +android::base::Result toRemoveUserRequest(const VehiclePropValue& propValue); +android::base::Result toUserIdentificationGetRequest( + const VehiclePropValue& propValue); +android::base::Result toUserIdentificationSetRequest( + const VehiclePropValue& propValue); + +// Below functions convert the User HAL structs to VehiclePropValues. On success, these functions +// return the pointer to VehiclePropValue. Otherwise, they return nullptr. +std::unique_ptr toVehiclePropValue(const SwitchUserRequest& request); +std::unique_ptr toVehiclePropValue(const InitialUserInfoResponse& response); +std::unique_ptr toVehiclePropValue(const SwitchUserResponse& response); +std::unique_ptr toVehiclePropValue(const CreateUserResponse& response); +std::unique_ptr toVehiclePropValue(const UserIdentificationResponse& response); + +} // namespace user_hal_helper + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_impl_UserHalHelper_H_ diff --git a/automotive/vehicle/2.0/utils/tests/UserHalHelper_test.cpp b/automotive/vehicle/2.0/utils/tests/UserHalHelper_test.cpp new file mode 100644 index 0000000000..7da87a23c8 --- /dev/null +++ b/automotive/vehicle/2.0/utils/tests/UserHalHelper_test.cpp @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UserHalHelper.h" + +#include + +#include + +#include "gmock/gmock.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace user_hal_helper { + +namespace { + +using testing::Eq; +using testing::Gt; +using testing::IsNull; +using testing::NotNull; +using testing::Pointee; + +constexpr int32_t INITIAL_USER_INFO = static_cast(VehicleProperty::INITIAL_USER_INFO); +constexpr int32_t SWITCH_USER = static_cast(VehicleProperty::SWITCH_USER); +constexpr int32_t CREATE_USER = static_cast(VehicleProperty::CREATE_USER); +constexpr int32_t REMOVE_USER = static_cast(VehicleProperty::REMOVE_USER); +constexpr int32_t USER_IDENTIFICATION_ASSOCIATION = + static_cast(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION); + +constexpr int32_t FIRST_BOOT_AFTER_OTA = + static_cast(InitialUserInfoRequestType::FIRST_BOOT_AFTER_OTA); +constexpr int32_t LEGACY_ANDROID_SWITCH = + static_cast(SwitchUserMessageType::LEGACY_ANDROID_SWITCH); +constexpr int32_t VEHICLE_REQUEST = static_cast(SwitchUserMessageType::VEHICLE_REQUEST); + +constexpr int32_t GUEST_USER = static_cast(UserFlags::GUEST); +constexpr int32_t NONE_USER = static_cast(UserFlags::NONE); +constexpr int32_t SYSTEM_USER = static_cast(UserFlags::SYSTEM); + +constexpr int32_t USER_ID_ASSOC_KEY_FOB = + static_cast(UserIdentificationAssociationType::KEY_FOB); +constexpr int32_t USER_ID_ASSOC_CUSTOM_1 = + static_cast(UserIdentificationAssociationType::CUSTOM_1); + +constexpr int32_t USER_ID_ASSOC_SET_CURRENT_USER = + static_cast(UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER); +constexpr int32_t USER_ID_ASSOC_UNSET_CURRENT_USER = + static_cast(UserIdentificationAssociationSetValue::DISASSOCIATE_CURRENT_USER); + +constexpr int32_t USER_ID_ASSOC_CURRENT_USER = + static_cast(UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER); +constexpr int32_t USER_ID_ASSOC_NO_USER = + static_cast(UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER); + +} // namespace + +TEST(UserHalHelperTest, TestToInitialUserInfoRequest) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER, + 10, NONE_USER}}, + }; + InitialUserInfoRequest expected{ + .requestId = 23, + .requestType = InitialUserInfoRequestType::FIRST_BOOT_AFTER_OTA, + .usersInfo = {{10, UserFlags::NONE}, + 2, + {{0, UserFlags::SYSTEM}, {10, UserFlags::NONE}}}, + }; + + auto actual = toInitialUserInfoRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INT32_MAX, + .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER, + 10, NONE_USER}}, + }; + + auto actual = toInitialUserInfoRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithInvalidRequestType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, INT32_MAX, 10, NONE_USER, 2, 0, SYSTEM_USER, 10, + NONE_USER}}, + }; + + auto actual = toInitialUserInfoRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on invalid request type"; +} + +TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithInvalidUserFlag) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, SYSTEM_USER, + 10, INT32_MAX}}, + }; + + auto actual = toInitialUserInfoRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on invalid user flags"; +} + +TEST(UserHalHelperTest, TestFailsToInitialUserInfoRequestWithIncompleteUsersInfo) { + VehiclePropValue propValueMissingSecondUserInfo{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, 10, NONE_USER, 2, 0, + SYSTEM_USER /*Missing 2nd UserInfo*/}}, + }; + + auto actual = toInitialUserInfoRequest(propValueMissingSecondUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info"; + + VehiclePropValue propValueMissingUsersInfo{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, FIRST_BOOT_AFTER_OTA, /*Missing UsersInfo*/}}, + }; + + actual = toInitialUserInfoRequest(propValueMissingUsersInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing users info"; +} + +TEST(UserHalHelperTest, TestToSwitchUserRequest) { + VehiclePropValue propValue{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2, + 0, SYSTEM_USER, 10, NONE_USER}}, + }; + SwitchUserRequest expected{ + .requestId = 23, + .messageType = SwitchUserMessageType::LEGACY_ANDROID_SWITCH, + .targetUser = {0, UserFlags::SYSTEM}, + .usersInfo = {{10, UserFlags::NONE}, + 2, + {{0, UserFlags::SYSTEM}, {10, UserFlags::NONE}}}, + }; + + auto actual = toSwitchUserRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2, + 0, SYSTEM_USER, 10, NONE_USER}}, + }; + + auto actual = toSwitchUserRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithInvalidMessageType) { + VehiclePropValue propValueIncompatibleMessageType{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, VEHICLE_REQUEST, 0, SYSTEM_USER, 10, NONE_USER, 2, 0, + SYSTEM_USER, 10, NONE_USER}}, + }; + + auto actual = toSwitchUserRequest(propValueIncompatibleMessageType); + + EXPECT_FALSE(actual.ok()) << "No error returned on incompatible message type"; + + VehiclePropValue propValueInvalidMessageType{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, INT32_MAX, 0, SYSTEM_USER, 10, NONE_USER, 2, 0, + SYSTEM_USER, 10, NONE_USER}}, + }; + + actual = toSwitchUserRequest(propValueInvalidMessageType); + + EXPECT_FALSE(actual.ok()) << "No error returned on invalid message type"; +} + +TEST(UserHalHelperTest, TestFailsToSwitchUserRequestWithIncompleteUsersInfo) { + VehiclePropValue propValueMissingSecondUserInfo{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, 10, NONE_USER, 2, + 0, SYSTEM_USER, + /*Missing 2nd UserInfo*/}}, + }; + + auto actual = toSwitchUserRequest(propValueMissingSecondUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info"; + + VehiclePropValue propValueMissingUsersInfo{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, 0, SYSTEM_USER, + /*Missing UsersInfo*/}}, + }; + + actual = toSwitchUserRequest(propValueMissingUsersInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing users info"; + + VehiclePropValue propValueMissingTargetUser{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, LEGACY_ANDROID_SWITCH, /*Missing target UserInfo*/}}, + }; + + actual = toSwitchUserRequest(propValueMissingTargetUser); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing target user info"; +} + +TEST(UserHalHelperTest, TestToCreateUserRequest) { + VehiclePropValue propValue{ + .prop = CREATE_USER, + .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10, + NONE_USER}, + .stringValue = "Guest11"}, + }; + CreateUserRequest expected{ + .requestId = 23, + .newUserInfo = {11, UserFlags::GUEST}, + .newUserName = "Guest11", + .usersInfo = {{10, UserFlags::NONE}, + 2, + {{0, UserFlags::SYSTEM}, {10, UserFlags::NONE}}}, + }; + + auto actual = toCreateUserRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToCreateUserRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10, + NONE_USER}, + .stringValue = "Guest11"}, + }; + + auto actual = toCreateUserRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToCreateUserRequestWithIncompleteUsersInfo) { + VehiclePropValue propValueMissingSecondUserInfo{ + .prop = CREATE_USER, + .value = {.int32Values = {23, 11, GUEST_USER, 10, NONE_USER, 2, 0, + SYSTEM_USER /*Missing 2nd UserInfo*/}, + .stringValue = "Guest11"}, + }; + + auto actual = toCreateUserRequest(propValueMissingSecondUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info"; + + VehiclePropValue propValueMissingUsersInfo{ + .prop = CREATE_USER, + .value = {.int32Values = {23, 11, GUEST_USER, /*Missing UsersInfo*/}, + .stringValue = "Guest11"}, + }; + + actual = toCreateUserRequest(propValueMissingUsersInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing users info"; + + VehiclePropValue propValueMissingCreateUserInfo{ + .prop = CREATE_USER, + .value = {.int32Values = {23, /*Missing create UserInfo*/}, .stringValue = "Guest11"}, + }; + + actual = toCreateUserRequest(propValueMissingCreateUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing create user info"; +} + +TEST(UserHalHelperTest, TestToRemoveUserRequest) { + VehiclePropValue propValue{ + .prop = REMOVE_USER, + .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10, + NONE_USER}}, + }; + RemoveUserRequest expected{ + .requestId = 23, + .removedUserInfo = {10, UserFlags::NONE}, + .usersInfo = {{10, UserFlags::NONE}, + 2, + {{0, UserFlags::SYSTEM}, {10, UserFlags::NONE}}}, + }; + + auto actual = toRemoveUserRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToRemoveUserRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0, SYSTEM_USER, 10, + NONE_USER}}, + }; + + auto actual = toRemoveUserRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToRemoveUserRequestWithIncompleteUsersInfo) { + VehiclePropValue propValueMissingSecondUserInfo{ + .prop = REMOVE_USER, + .value = {.int32Values = {23, 10, NONE_USER, 10, NONE_USER, 2, 0, + SYSTEM_USER /*Missing 2nd UserInfo*/}}, + }; + + auto actual = toRemoveUserRequest(propValueMissingSecondUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second user info"; + + VehiclePropValue propValueMissingUsersInfo{ + .prop = REMOVE_USER, + .value = {.int32Values = {23, 10, NONE_USER, /*Missing UsersInfo*/}}, + }; + + actual = toRemoveUserRequest(propValueMissingUsersInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing users info"; + + VehiclePropValue propValueMissingRemoveUserInfo{ + .prop = REMOVE_USER, + .value = {.int32Values = {23, /*Missing remove UserInfo*/}}, + }; + + actual = toRemoveUserRequest(propValueMissingRemoveUserInfo); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing remove user info"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequest) { + VehiclePropValue propValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB, + USER_ID_ASSOC_CUSTOM_1}}, + }; + UserIdentificationGetRequest expected{ + .requestId = 23, + .userInfo = {10, UserFlags::NONE}, + .numberAssociationTypes = 2, + .associationTypes = {UserIdentificationAssociationType::KEY_FOB, + UserIdentificationAssociationType::CUSTOM_1}, + }; + + auto actual = toUserIdentificationGetRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB, + USER_ID_ASSOC_CUSTOM_1}}, + }; + + auto actual = toUserIdentificationGetRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithInvalidAssociationTypes) { + VehiclePropValue propValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 1, INT32_MAX}}, + }; + + auto actual = toUserIdentificationGetRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on invalid association type"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithIncompleteAssociationTypes) { + VehiclePropValue propValueMissingSecondAssociationType{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 2, + USER_ID_ASSOC_KEY_FOB /*Missing 2nd association type*/}}, + }; + + auto actual = toUserIdentificationGetRequest(propValueMissingSecondAssociationType); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second association type"; + + VehiclePropValue propValueMissingNumberAssociationTypes{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, /*Missing number association types*/}}, + }; + + actual = toUserIdentificationGetRequest(propValueMissingNumberAssociationTypes); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationGetRequestWithMissingUserInfo) { + VehiclePropValue propValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, /*Missing user info*/}}, + }; + + auto actual = toUserIdentificationGetRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing UserInfo"; +} + +TEST(UserHalHelperTest, TestToUserIdentificationSetRequest) { + VehiclePropValue propValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB, + USER_ID_ASSOC_SET_CURRENT_USER, USER_ID_ASSOC_CUSTOM_1, + USER_ID_ASSOC_UNSET_CURRENT_USER}}, + }; + UserIdentificationSetRequest expected{ + .requestId = 23, + .userInfo = {10, UserFlags::NONE}, + .numberAssociations = 2, + .associations = {{UserIdentificationAssociationType::KEY_FOB, + UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER}, + {UserIdentificationAssociationType::CUSTOM_1, + UserIdentificationAssociationSetValue::DISASSOCIATE_CURRENT_USER}}, + }; + + auto actual = toUserIdentificationSetRequest(propValue); + + ASSERT_TRUE(actual.ok()) << actual.error().message(); + EXPECT_THAT(actual.value(), Eq(expected)); +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithMismatchingPropType) { + VehiclePropValue propValue{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB, + USER_ID_ASSOC_SET_CURRENT_USER, USER_ID_ASSOC_CUSTOM_1, + USER_ID_ASSOC_UNSET_CURRENT_USER}}, + }; + + auto actual = toUserIdentificationSetRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on mismatching property type"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithInvalidAssociations) { + VehiclePropValue propValueInvalidAssociationType{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 1, INT32_MAX, + USER_ID_ASSOC_SET_CURRENT_USER}}, + }; + + auto actual = toUserIdentificationSetRequest(propValueInvalidAssociationType); + + EXPECT_FALSE(actual.ok()) << "No error returned on invalid association type"; + + VehiclePropValue propValueInvalidAssociationValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, USER_ID_ASSOC_KEY_FOB, INT32_MAX}}, + }; + + actual = toUserIdentificationSetRequest(propValueInvalidAssociationValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithIncompleteAssociations) { + VehiclePropValue propValueMissingSecondAssociationType{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, 2, USER_ID_ASSOC_KEY_FOB, + USER_ID_ASSOC_SET_CURRENT_USER, + /*Missing 2nd association*/}}, + }; + + auto actual = toUserIdentificationSetRequest(propValueMissingSecondAssociationType); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing second association type"; + + VehiclePropValue propValueMissingNumberAssociationTypes{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 10, NONE_USER, /*Missing number associations*/}}, + }; + + actual = toUserIdentificationSetRequest(propValueMissingNumberAssociationTypes); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing number association types"; +} + +TEST(UserHalHelperTest, TestFailsToUserIdentificationSetRequestWithMissingUserInfo) { + VehiclePropValue propValue{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, /*Missing user info*/}}, + }; + + auto actual = toUserIdentificationSetRequest(propValue); + + EXPECT_FALSE(actual.ok()) << "No error returned on missing UserInfo"; +} + +TEST(UserHalHelperTest, TestSwitchUserRequestToVehiclePropValue) { + SwitchUserRequest request{ + .requestId = 23, + .messageType = SwitchUserMessageType::VEHICLE_REQUEST, + .targetUser = {11, UserFlags::GUEST}, + }; + VehiclePropValue expected{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, + static_cast(SwitchUserMessageType::VEHICLE_REQUEST), + 11}}, + }; + + auto actual = toVehiclePropValue(request); + + ASSERT_THAT(actual, NotNull()); + EXPECT_THAT(actual->timestamp, Gt(0)); + // Don't rely on real timestamp in tests as the expected and actual objects won't have the same + // timestamps. Thus remove the timestamps before comparing them. + actual->timestamp = 0; + EXPECT_THAT(actual, Pointee(Eq(expected))); +} + +TEST(UserHalHelperTest, TestFailsSwitchUserRequestToVehiclePropValueWithIncompatibleMessageType) { + SwitchUserRequest request{ + .requestId = 23, + .messageType = SwitchUserMessageType::VEHICLE_RESPONSE, + .targetUser = {11, UserFlags::GUEST}, + }; + + auto actual = toVehiclePropValue(request); + + EXPECT_THAT(actual, IsNull()); +} + +TEST(UserHalHelperTest, TestInitialUserInfoResponseToVehiclePropValue) { + InitialUserInfoResponse response{ + .requestId = 23, + .action = InitialUserInfoResponseAction::CREATE, + .userToSwitchOrCreate = {11, UserFlags::GUEST}, + .userLocales = "en-US,pt-BR", + .userNameToCreate = "Owner", + }; + VehiclePropValue expected{ + .prop = INITIAL_USER_INFO, + .value = {.int32Values = {23, + static_cast(InitialUserInfoResponseAction::CREATE), + 11, GUEST_USER}, + .stringValue = "en-US,pt-BR||Owner"}, + }; + + auto actual = toVehiclePropValue(response); + + ASSERT_THAT(actual, NotNull()); + EXPECT_THAT(actual->timestamp, Gt(0)); + actual->timestamp = 0; + EXPECT_THAT(actual, Pointee(Eq(expected))); +} + +TEST(UserHalHelperTest, TestSwitchUserResponseToVehiclePropValue) { + SwitchUserResponse response{ + .requestId = 23, + .messageType = SwitchUserMessageType::VEHICLE_RESPONSE, + .status = SwitchUserStatus::FAILURE, + .errorMessage = "random error", + }; + VehiclePropValue expected{ + .prop = SWITCH_USER, + .value = {.int32Values = {23, + static_cast(SwitchUserMessageType::VEHICLE_RESPONSE), + static_cast(SwitchUserStatus::FAILURE)}, + .stringValue = "random error"}, + }; + + auto actual = toVehiclePropValue(response); + + ASSERT_THAT(actual, NotNull()); + EXPECT_THAT(actual->timestamp, Gt(0)); + actual->timestamp = 0; + EXPECT_THAT(actual, Pointee(Eq(expected))); +} + +TEST(UserHalHelperTest, TestCreateUserResponseToVehiclePropValue) { + CreateUserResponse response{ + .requestId = 23, + .status = CreateUserStatus::FAILURE, + .errorMessage = "random error", + }; + VehiclePropValue expected{ + .prop = CREATE_USER, + .value = {.int32Values = {23, static_cast(CreateUserStatus::FAILURE)}, + .stringValue = "random error"}, + }; + + auto actual = toVehiclePropValue(response); + + ASSERT_THAT(actual, NotNull()); + EXPECT_THAT(actual->timestamp, Gt(0)); + actual->timestamp = 0; + EXPECT_THAT(actual, Pointee(Eq(expected))); +} + +TEST(UserHalHelperTest, TestUserIdentificationResponseToVehiclePropValue) { + UserIdentificationResponse response{ + .requestId = 23, + .numberAssociation = 2, + .associations = {{UserIdentificationAssociationType::KEY_FOB, + UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER}, + {UserIdentificationAssociationType::CUSTOM_1, + UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER}}, + .errorMessage = "random error", + }; + VehiclePropValue expected{ + .prop = USER_IDENTIFICATION_ASSOCIATION, + .value = {.int32Values = {23, 2, USER_ID_ASSOC_KEY_FOB, USER_ID_ASSOC_CURRENT_USER, + USER_ID_ASSOC_CUSTOM_1, USER_ID_ASSOC_NO_USER}, + .stringValue = "random error"}, + }; + + auto actual = toVehiclePropValue(response); + + ASSERT_THAT(actual, NotNull()); + EXPECT_THAT(actual->timestamp, Gt(0)); + actual->timestamp = 0; + EXPECT_THAT(actual, Pointee(Eq(expected))); +} + +} // namespace user_hal_helper + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android