| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "DefaultVehicleHal" |
| #define ATRACE_TAG ATRACE_TAG_HAL |
| |
| #include <DefaultVehicleHal.h> |
| |
| #include <LargeParcelableBase.h> |
| #include <VehicleHalTypes.h> |
| #include <VehicleUtils.h> |
| |
| #include <android-base/result.h> |
| #include <android-base/stringprintf.h> |
| #include <android/binder_ibinder.h> |
| #include <private/android_filesystem_config.h> |
| #include <utils/Log.h> |
| #include <utils/SystemClock.h> |
| #include <utils/Trace.h> |
| |
| #include <inttypes.h> |
| #include <chrono> |
| #include <set> |
| #include <unordered_set> |
| |
| namespace android { |
| namespace hardware { |
| namespace automotive { |
| namespace vehicle { |
| |
| namespace { |
| |
| using ::aidl::android::hardware::automotive::vehicle::GetValueRequest; |
| using ::aidl::android::hardware::automotive::vehicle::GetValueRequests; |
| using ::aidl::android::hardware::automotive::vehicle::GetValueResult; |
| using ::aidl::android::hardware::automotive::vehicle::GetValueResults; |
| using ::aidl::android::hardware::automotive::vehicle::SetValueRequest; |
| using ::aidl::android::hardware::automotive::vehicle::SetValueRequests; |
| using ::aidl::android::hardware::automotive::vehicle::SetValueResult; |
| using ::aidl::android::hardware::automotive::vehicle::SetValueResults; |
| using ::aidl::android::hardware::automotive::vehicle::StatusCode; |
| using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions; |
| using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfigs; |
| using ::aidl::android::hardware::automotive::vehicle::VehicleProperty; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus; |
| using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; |
| using ::android::automotive::car_binder_lib::LargeParcelableBase; |
| using ::android::base::Error; |
| using ::android::base::expected; |
| using ::android::base::Result; |
| using ::android::base::StringPrintf; |
| |
| using ::ndk::ScopedAIBinder_DeathRecipient; |
| using ::ndk::ScopedAStatus; |
| |
| std::string toString(const std::unordered_set<int64_t>& values) { |
| std::string str = ""; |
| for (auto it = values.begin(); it != values.end(); it++) { |
| str += std::to_string(*it); |
| if (std::next(it, 1) != values.end()) { |
| str += ", "; |
| } |
| } |
| return str; |
| } |
| |
| float getDefaultSampleRateHz(float sampleRateHz, float minSampleRateHz, float maxSampleRateHz) { |
| if (sampleRateHz < minSampleRateHz) { |
| return minSampleRateHz; |
| } |
| if (sampleRateHz > maxSampleRateHz) { |
| return maxSampleRateHz; |
| } |
| return sampleRateHz; |
| } |
| |
| } // namespace |
| |
| DefaultVehicleHal::DefaultVehicleHal(std::unique_ptr<IVehicleHardware> vehicleHardware) |
| : mVehicleHardware(std::move(vehicleHardware)), |
| mPendingRequestPool(std::make_shared<PendingRequestPool>(TIMEOUT_IN_NANO)) { |
| if (!getAllPropConfigsFromHardware()) { |
| return; |
| } |
| |
| 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, 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>( |
| [subscriptionManagerCopy](std::vector<SetValueErrorEvent> errorEvents) { |
| onPropertySetErrorEvent(subscriptionManagerCopy, errorEvents); |
| })); |
| |
| // Register heartbeat event. |
| mRecurrentAction = std::make_shared<std::function<void()>>( |
| [vehicleHardwarePtr, subscriptionManagerCopy]() { |
| checkHealth(vehicleHardwarePtr, subscriptionManagerCopy); |
| }); |
| mRecurrentTimer.registerTimerCallback(HEART_BEAT_INTERVAL_IN_NANO, mRecurrentAction); |
| |
| mBinderLifecycleHandler = std::make_unique<BinderLifecycleHandler>(); |
| mOnBinderDiedUnlinkedHandlerThread = std::thread([this] { onBinderDiedUnlinkedHandler(); }); |
| mDeathRecipient = ScopedAIBinder_DeathRecipient( |
| AIBinder_DeathRecipient_new(&DefaultVehicleHal::onBinderDied)); |
| AIBinder_DeathRecipient_setOnUnlinked(mDeathRecipient.get(), |
| &DefaultVehicleHal::onBinderUnlinked); |
| } |
| |
| DefaultVehicleHal::~DefaultVehicleHal() { |
| // Delete the deathRecipient so that onBinderDied would not be called to reference 'this'. |
| mDeathRecipient = ScopedAIBinder_DeathRecipient(); |
| mBinderEvents.deactivate(); |
| if (mOnBinderDiedUnlinkedHandlerThread.joinable()) { |
| mOnBinderDiedUnlinkedHandlerThread.join(); |
| } |
| // 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, |
| std::vector<VehiclePropValue>&& updatedValues) { |
| ATRACE_CALL(); |
| auto manager = subscriptionManager.lock(); |
| if (manager == nullptr) { |
| ALOGW("the SubscriptionManager is destroyed, DefaultVehicleHal is ending"); |
| return; |
| } |
| auto updatedValuesByClients = manager->getSubscribedClients(std::move(updatedValues)); |
| for (auto& [callback, values] : updatedValuesByClients) { |
| SubscriptionClient::sendUpdatedValues(callback, std::move(values)); |
| } |
| } |
| |
| void DefaultVehicleHal::onPropertySetErrorEvent( |
| const std::weak_ptr<SubscriptionManager>& subscriptionManager, |
| const std::vector<SetValueErrorEvent>& errorEvents) { |
| auto manager = subscriptionManager.lock(); |
| if (manager == nullptr) { |
| ALOGW("the SubscriptionManager is destroyed, DefaultVehicleHal is ending"); |
| return; |
| } |
| auto vehiclePropErrorsByClient = manager->getSubscribedClientsForErrorEvents(errorEvents); |
| for (auto& [callback, vehiclePropErrors] : vehiclePropErrorsByClient) { |
| SubscriptionClient::sendPropertySetErrors(callback, std::move(vehiclePropErrors)); |
| } |
| } |
| |
| template <class T> |
| std::shared_ptr<T> DefaultVehicleHal::getOrCreateClient( |
| std::unordered_map<const AIBinder*, std::shared_ptr<T>>* clients, |
| const CallbackType& callback, std::shared_ptr<PendingRequestPool> pendingRequestPool) { |
| const AIBinder* clientId = callback->asBinder().get(); |
| if (clients->find(clientId) == clients->end()) { |
| (*clients)[clientId] = std::make_shared<T>(pendingRequestPool, callback); |
| } |
| return (*clients)[clientId]; |
| } |
| |
| bool DefaultVehicleHal::monitorBinderLifeCycleLocked(const AIBinder* clientId) { |
| OnBinderDiedContext* contextPtr = nullptr; |
| if (mOnBinderDiedContexts.find(clientId) != mOnBinderDiedContexts.end()) { |
| return mBinderLifecycleHandler->isAlive(clientId); |
| } else { |
| std::unique_ptr<OnBinderDiedContext> context = std::make_unique<OnBinderDiedContext>( |
| OnBinderDiedContext{.vhal = this, .clientId = clientId}); |
| // We know context must be alive when we use contextPtr because context would only |
| // be removed in OnBinderUnlinked, which must be called after OnBinderDied. |
| contextPtr = context.get(); |
| // Insert into a map to keep the context object alive. |
| mOnBinderDiedContexts[clientId] = std::move(context); |
| } |
| |
| // If this function fails, onBinderUnlinked would be called to remove the added context. |
| binder_status_t status = mBinderLifecycleHandler->linkToDeath( |
| const_cast<AIBinder*>(clientId), mDeathRecipient.get(), static_cast<void*>(contextPtr)); |
| if (status == STATUS_OK) { |
| return true; |
| } |
| ALOGE("failed to call linkToDeath on client binder, client may already died, status: %d", |
| static_cast<int>(status)); |
| return false; |
| } |
| |
| void DefaultVehicleHal::onBinderDied(void* cookie) { |
| OnBinderDiedContext* context = reinterpret_cast<OnBinderDiedContext*>(cookie); |
| // To be handled in mOnBinderDiedUnlinkedHandlerThread. We cannot handle the event in the same |
| // thread because we might be holding the mLock the handler requires. |
| context->vhal->mBinderEvents.push( |
| BinderDiedUnlinkedEvent{/*forOnBinderDied=*/true, context->clientId}); |
| } |
| |
| void DefaultVehicleHal::onBinderDiedWithContext(const AIBinder* clientId) { |
| std::scoped_lock<std::mutex> lockGuard(mLock); |
| ALOGD("binder died, client ID: %p", clientId); |
| mSetValuesClients.erase(clientId); |
| mGetValuesClients.erase(clientId); |
| mSubscriptionManager->unsubscribe(clientId); |
| } |
| |
| void DefaultVehicleHal::onBinderUnlinked(void* cookie) { |
| OnBinderDiedContext* context = reinterpret_cast<OnBinderDiedContext*>(cookie); |
| // To be handled in mOnBinderDiedUnlinkedHandlerThread. We cannot handle the event in the same |
| // thread because we might be holding the mLock the handler requires. |
| context->vhal->mBinderEvents.push( |
| BinderDiedUnlinkedEvent{/*forOnBinderDied=*/false, context->clientId}); |
| } |
| |
| void DefaultVehicleHal::onBinderUnlinkedWithContext(const AIBinder* clientId) { |
| ALOGD("binder unlinked"); |
| std::scoped_lock<std::mutex> lockGuard(mLock); |
| // Delete the context associated with this cookie. |
| mOnBinderDiedContexts.erase(clientId); |
| } |
| |
| void DefaultVehicleHal::onBinderDiedUnlinkedHandler() { |
| while (mBinderEvents.waitForItems()) { |
| for (BinderDiedUnlinkedEvent& event : mBinderEvents.flush()) { |
| if (event.forOnBinderDied) { |
| onBinderDiedWithContext(event.clientId); |
| } else { |
| onBinderUnlinkedWithContext(event.clientId); |
| } |
| } |
| } |
| } |
| |
| template std::shared_ptr<DefaultVehicleHal::GetValuesClient> |
| DefaultVehicleHal::getOrCreateClient<DefaultVehicleHal::GetValuesClient>( |
| std::unordered_map<const AIBinder*, std::shared_ptr<GetValuesClient>>* clients, |
| const CallbackType& callback, std::shared_ptr<PendingRequestPool> pendingRequestPool); |
| template std::shared_ptr<DefaultVehicleHal::SetValuesClient> |
| DefaultVehicleHal::getOrCreateClient<DefaultVehicleHal::SetValuesClient>( |
| std::unordered_map<const AIBinder*, std::shared_ptr<SetValuesClient>>* clients, |
| const CallbackType& callback, std::shared_ptr<PendingRequestPool> pendingRequestPool); |
| |
| void DefaultVehicleHal::setTimeout(int64_t timeoutInNano) { |
| mPendingRequestPool = std::make_unique<PendingRequestPool>(timeoutInNano); |
| } |
| |
| bool DefaultVehicleHal::getAllPropConfigsFromHardware() { |
| auto configs = mVehicleHardware->getAllPropertyConfigs(); |
| for (auto& config : configs) { |
| mConfigsByPropId[config.prop] = config; |
| } |
| VehiclePropConfigs vehiclePropConfigs; |
| vehiclePropConfigs.payloads = std::move(configs); |
| auto result = LargeParcelableBase::parcelableToStableLargeParcelable(vehiclePropConfigs); |
| if (!result.ok()) { |
| ALOGE("failed to convert configs to shared memory file, error: %s, code: %d", |
| result.error().message().c_str(), static_cast<int>(result.error().code())); |
| mConfigFile = nullptr; |
| return false; |
| } |
| |
| if (result.value() != nullptr) { |
| mConfigFile = std::move(result.value()); |
| } |
| return true; |
| } |
| |
| ScopedAStatus DefaultVehicleHal::getAllPropConfigs(VehiclePropConfigs* output) { |
| if (mConfigFile != nullptr) { |
| output->payloads.clear(); |
| output->sharedMemoryFd.set(dup(mConfigFile->get())); |
| return ScopedAStatus::ok(); |
| } |
| output->payloads.reserve(mConfigsByPropId.size()); |
| for (const auto& [_, config] : mConfigsByPropId) { |
| output->payloads.push_back(config); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| Result<const VehiclePropConfig*> DefaultVehicleHal::getConfig(int32_t propId) const { |
| auto it = mConfigsByPropId.find(propId); |
| if (it == mConfigsByPropId.end()) { |
| return Error() << "no config for property, ID: " << propId; |
| } |
| return &(it->second); |
| } |
| |
| Result<void> DefaultVehicleHal::checkProperty(const VehiclePropValue& propValue) { |
| int32_t propId = propValue.prop; |
| auto result = getConfig(propId); |
| if (!result.ok()) { |
| return result.error(); |
| } |
| const VehiclePropConfig* config = result.value(); |
| const VehicleAreaConfig* areaConfig = getAreaConfig(propValue, *config); |
| if (!isGlobalProp(propId) && areaConfig == nullptr) { |
| // Ignore areaId for global property. For non global property, check whether areaId is |
| // allowed. areaId must appear in areaConfig. |
| return Error() << "invalid area ID: " << propValue.areaId << " for prop ID: " << propId |
| << ", not listed in config"; |
| } |
| if (auto result = checkPropValue(propValue, config); !result.ok()) { |
| return Error() << "invalid property value: " << propValue.toString() |
| << ", error: " << getErrorMsg(result); |
| } |
| if (auto result = checkValueRange(propValue, areaConfig); !result.ok()) { |
| return Error() << "property value out of range: " << propValue.toString() |
| << ", error: " << getErrorMsg(result); |
| } |
| return {}; |
| } |
| |
| ScopedAStatus DefaultVehicleHal::getValues(const CallbackType& callback, |
| const GetValueRequests& requests) { |
| ATRACE_CALL(); |
| if (callback == nullptr) { |
| return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| } |
| expected<LargeParcelableBase::BorrowedOwnedObject<GetValueRequests>, ScopedAStatus> |
| deserializedResults = fromStableLargeParcelable(requests); |
| if (!deserializedResults.ok()) { |
| ALOGE("getValues: failed to parse getValues requests"); |
| return std::move(deserializedResults.error()); |
| } |
| const std::vector<GetValueRequest>& getValueRequests = |
| deserializedResults.value().getObject()->payloads; |
| |
| auto maybeRequestIds = checkDuplicateRequests(getValueRequests); |
| if (!maybeRequestIds.ok()) { |
| ALOGE("getValues: duplicate request ID"); |
| return toScopedAStatus(maybeRequestIds, StatusCode::INVALID_ARG); |
| } |
| |
| // A list of failed result we already know before sending to hardware. |
| std::vector<GetValueResult> failedResults; |
| // The list of requests that we would send to hardware. |
| std::vector<GetValueRequest> hardwareRequests; |
| |
| for (const auto& request : getValueRequests) { |
| if (auto result = checkReadPermission(request.prop); !result.ok()) { |
| ALOGW("property does not support reading: %s", getErrorMsg(result).c_str()); |
| failedResults.push_back(GetValueResult{ |
| .requestId = request.requestId, |
| .status = getErrorCode(result), |
| .prop = {}, |
| }); |
| } else { |
| hardwareRequests.push_back(request); |
| } |
| } |
| |
| // The set of request Ids that we would send to hardware. |
| std::unordered_set<int64_t> hardwareRequestIds; |
| for (const auto& request : hardwareRequests) { |
| hardwareRequestIds.insert(request.requestId); |
| } |
| |
| std::shared_ptr<GetValuesClient> client; |
| { |
| // Lock to make sure onBinderDied would not be called concurrently. |
| std::scoped_lock lockGuard(mLock); |
| if (!monitorBinderLifeCycleLocked(callback->asBinder().get())) { |
| return ScopedAStatus::fromExceptionCodeWithMessage(EX_TRANSACTION_FAILED, |
| "client died"); |
| } |
| |
| client = getOrCreateClient(&mGetValuesClients, callback, mPendingRequestPool); |
| } |
| |
| // Register the pending hardware requests and also check for duplicate request Ids. |
| if (auto addRequestResult = client->addRequests(hardwareRequestIds); !addRequestResult.ok()) { |
| ALOGE("getValues[%s]: failed to add pending requests, error: %s", |
| toString(hardwareRequestIds).c_str(), getErrorMsg(addRequestResult).c_str()); |
| return toScopedAStatus(addRequestResult); |
| } |
| |
| if (!failedResults.empty()) { |
| // First send the failed results we already know back to the client. |
| client->sendResults(std::move(failedResults)); |
| } |
| |
| if (hardwareRequests.empty()) { |
| return ScopedAStatus::ok(); |
| } |
| |
| if (StatusCode status = |
| mVehicleHardware->getValues(client->getResultCallback(), hardwareRequests); |
| status != StatusCode::OK) { |
| // If the hardware returns error, finish all the pending requests for this request because |
| // we never expect hardware to call callback for these requests. |
| client->tryFinishRequests(hardwareRequestIds); |
| ALOGE("getValues[%s]: failed to get value from VehicleHardware, status: %d", |
| toString(hardwareRequestIds).c_str(), toInt(status)); |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| toInt(status), "failed to get value from VehicleHardware"); |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus DefaultVehicleHal::setValues(const CallbackType& callback, |
| const SetValueRequests& requests) { |
| ATRACE_CALL(); |
| if (callback == nullptr) { |
| return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| } |
| expected<LargeParcelableBase::BorrowedOwnedObject<SetValueRequests>, ScopedAStatus> |
| deserializedResults = fromStableLargeParcelable(requests); |
| if (!deserializedResults.ok()) { |
| ALOGE("setValues: failed to parse setValues requests"); |
| return std::move(deserializedResults.error()); |
| } |
| const std::vector<SetValueRequest>& setValueRequests = |
| deserializedResults.value().getObject()->payloads; |
| |
| // A list of failed result we already know before sending to hardware. |
| std::vector<SetValueResult> failedResults; |
| // The list of requests that we would send to hardware. |
| std::vector<SetValueRequest> hardwareRequests; |
| |
| auto maybeRequestIds = checkDuplicateRequests(setValueRequests); |
| if (!maybeRequestIds.ok()) { |
| ALOGE("setValues: duplicate request ID"); |
| return toScopedAStatus(maybeRequestIds, StatusCode::INVALID_ARG); |
| } |
| |
| for (auto& request : setValueRequests) { |
| int64_t requestId = request.requestId; |
| if (auto result = checkWritePermission(request.value); !result.ok()) { |
| ALOGW("property does not support writing: %s", getErrorMsg(result).c_str()); |
| failedResults.push_back(SetValueResult{ |
| .requestId = requestId, |
| .status = getErrorCode(result), |
| }); |
| continue; |
| } |
| if (auto result = checkProperty(request.value); !result.ok()) { |
| ALOGW("setValues[%" PRId64 "]: property is not valid: %s", requestId, |
| getErrorMsg(result).c_str()); |
| failedResults.push_back(SetValueResult{ |
| .requestId = requestId, |
| .status = StatusCode::INVALID_ARG, |
| }); |
| continue; |
| } |
| |
| hardwareRequests.push_back(request); |
| } |
| |
| // The set of request Ids that we would send to hardware. |
| std::unordered_set<int64_t> hardwareRequestIds; |
| for (const auto& request : hardwareRequests) { |
| hardwareRequestIds.insert(request.requestId); |
| } |
| |
| std::shared_ptr<SetValuesClient> client; |
| { |
| // Lock to make sure onBinderDied would not be called concurrently. |
| std::scoped_lock lockGuard(mLock); |
| if (!monitorBinderLifeCycleLocked(callback->asBinder().get())) { |
| return ScopedAStatus::fromExceptionCodeWithMessage(EX_TRANSACTION_FAILED, |
| "client died"); |
| } |
| client = getOrCreateClient(&mSetValuesClients, callback, mPendingRequestPool); |
| } |
| |
| // Register the pending hardware requests and also check for duplicate request Ids. |
| if (auto addRequestResult = client->addRequests(hardwareRequestIds); !addRequestResult.ok()) { |
| ALOGE("setValues[%s], failed to add pending requests, error: %s", |
| toString(hardwareRequestIds).c_str(), getErrorMsg(addRequestResult).c_str()); |
| return toScopedAStatus(addRequestResult); |
| } |
| |
| if (!failedResults.empty()) { |
| // First send the failed results we already know back to the client. |
| client->sendResults(std::move(failedResults)); |
| } |
| |
| if (hardwareRequests.empty()) { |
| return ScopedAStatus::ok(); |
| } |
| |
| if (StatusCode status = |
| mVehicleHardware->setValues(client->getResultCallback(), hardwareRequests); |
| status != StatusCode::OK) { |
| // If the hardware returns error, finish all the pending requests for this request because |
| // we never expect hardware to call callback for these requests. |
| client->tryFinishRequests(hardwareRequestIds); |
| ALOGE("setValues[%s], failed to set value to VehicleHardware, status: %d", |
| toString(hardwareRequestIds).c_str(), toInt(status)); |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| toInt(status), "failed to set value to VehicleHardware"); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| #define CHECK_DUPLICATE_REQUESTS(PROP_NAME) \ |
| do { \ |
| std::vector<int64_t> requestIds; \ |
| std::set<::aidl::android::hardware::automotive::vehicle::VehiclePropValue> requestProps; \ |
| for (const auto& request : requests) { \ |
| const auto& prop = request.PROP_NAME; \ |
| if (requestProps.count(prop) != 0) { \ |
| return ::android::base::Error() \ |
| << "duplicate request for property: " << prop.toString(); \ |
| } \ |
| requestProps.insert(prop); \ |
| requestIds.push_back(request.requestId); \ |
| } \ |
| return requestIds; \ |
| } while (0); |
| |
| ::android::base::Result<std::vector<int64_t>> DefaultVehicleHal::checkDuplicateRequests( |
| const std::vector<GetValueRequest>& requests) { |
| CHECK_DUPLICATE_REQUESTS(prop); |
| } |
| |
| ::android::base::Result<std::vector<int64_t>> DefaultVehicleHal::checkDuplicateRequests( |
| const std::vector<SetValueRequest>& requests) { |
| CHECK_DUPLICATE_REQUESTS(value); |
| } |
| |
| #undef CHECK_DUPLICATE_REQUESTS |
| |
| ScopedAStatus DefaultVehicleHal::getPropConfigs(const std::vector<int32_t>& props, |
| VehiclePropConfigs* output) { |
| std::vector<VehiclePropConfig> configs; |
| for (int32_t prop : props) { |
| if (mConfigsByPropId.find(prop) != mConfigsByPropId.end()) { |
| configs.push_back(mConfigsByPropId[prop]); |
| } else { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| toInt(StatusCode::INVALID_ARG), |
| StringPrintf("no config for property, ID: %" PRId32, prop).c_str()); |
| } |
| } |
| return vectorToStableLargeParcelable(std::move(configs), output); |
| } |
| |
| bool hasRequiredAccess(VehiclePropertyAccess access, VehiclePropertyAccess requiredAccess) { |
| return access == requiredAccess || access == VehiclePropertyAccess::READ_WRITE; |
| } |
| |
| bool areaConfigsHaveRequiredAccess(const std::vector<VehicleAreaConfig>& areaConfigs, |
| VehiclePropertyAccess requiredAccess) { |
| if (areaConfigs.empty()) { |
| return false; |
| } |
| for (VehicleAreaConfig areaConfig : areaConfigs) { |
| if (!hasRequiredAccess(areaConfig.access, requiredAccess)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| VhalResult<void> DefaultVehicleHal::checkSubscribeOptions( |
| const std::vector<SubscribeOptions>& options) { |
| for (const auto& option : options) { |
| int32_t propId = option.propId; |
| if (mConfigsByPropId.find(propId) == mConfigsByPropId.end()) { |
| return StatusError(StatusCode::INVALID_ARG) |
| << StringPrintf("no config for property, ID: %" PRId32, propId); |
| } |
| const VehiclePropConfig& config = mConfigsByPropId[propId]; |
| std::vector<VehicleAreaConfig> areaConfigs; |
| if (option.areaIds.empty()) { |
| areaConfigs = config.areaConfigs; |
| } else { |
| std::unordered_map<int, VehicleAreaConfig> areaConfigByAreaId; |
| for (const VehicleAreaConfig& areaConfig : config.areaConfigs) { |
| areaConfigByAreaId.emplace(areaConfig.areaId, areaConfig); |
| } |
| for (int areaId : option.areaIds) { |
| auto it = areaConfigByAreaId.find(areaId); |
| if (it != areaConfigByAreaId.end()) { |
| areaConfigs.push_back(it->second); |
| } else if (areaId != 0 || !areaConfigByAreaId.empty()) { |
| return StatusError(StatusCode::INVALID_ARG) |
| << StringPrintf("invalid area ID: %" PRId32 " for prop ID: %" PRId32 |
| ", not listed in config", |
| areaId, propId); |
| } |
| } |
| } |
| |
| if (config.changeMode != VehiclePropertyChangeMode::ON_CHANGE && |
| config.changeMode != VehiclePropertyChangeMode::CONTINUOUS) { |
| return StatusError(StatusCode::INVALID_ARG) |
| << "only support subscribing to ON_CHANGE or CONTINUOUS property"; |
| } |
| |
| // Either VehiclePropConfig.access or VehicleAreaConfig.access will be specified |
| if (!hasRequiredAccess(config.access, VehiclePropertyAccess::READ) && |
| !areaConfigsHaveRequiredAccess(areaConfigs, VehiclePropertyAccess::READ)) { |
| return StatusError(StatusCode::ACCESS_DENIED) |
| << StringPrintf("Property %" PRId32 " has no read access", propId); |
| } |
| |
| if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) { |
| float sampleRateHz = option.sampleRate; |
| float minSampleRateHz = config.minSampleRate; |
| float maxSampleRateHz = config.maxSampleRate; |
| float defaultRateHz = |
| getDefaultSampleRateHz(sampleRateHz, minSampleRateHz, maxSampleRateHz); |
| if (sampleRateHz != defaultRateHz) { |
| ALOGW("sample rate: %f HZ out of range, must be within %f HZ and %f HZ , set to %f " |
| "HZ", |
| sampleRateHz, minSampleRateHz, maxSampleRateHz, defaultRateHz); |
| sampleRateHz = defaultRateHz; |
| } |
| if (!SubscriptionManager::checkSampleRateHz(sampleRateHz)) { |
| return StatusError(StatusCode::INVALID_ARG) |
| << "invalid sample rate: " << sampleRateHz << " HZ"; |
| } |
| } |
| } |
| return {}; |
| } |
| |
| ScopedAStatus DefaultVehicleHal::subscribe(const CallbackType& callback, |
| const std::vector<SubscribeOptions>& options, |
| [[maybe_unused]] int32_t maxSharedMemoryFileCount) { |
| // TODO(b/205189110): Use shared memory file count. |
| if (callback == nullptr) { |
| return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| } |
| if (auto result = checkSubscribeOptions(options); !result.ok()) { |
| ALOGE("subscribe: invalid subscribe options: %s", getErrorMsg(result).c_str()); |
| return toScopedAStatus(result); |
| } |
| std::vector<SubscribeOptions> onChangeSubscriptions; |
| std::vector<SubscribeOptions> continuousSubscriptions; |
| for (const auto& option : options) { |
| int32_t propId = option.propId; |
| // We have already validate config exists. |
| const VehiclePropConfig& config = mConfigsByPropId[propId]; |
| |
| SubscribeOptions optionCopy = option; |
| // If areaIds is empty, subscribe to all areas. |
| if (optionCopy.areaIds.empty() && !isGlobalProp(propId)) { |
| for (const auto& areaConfig : config.areaConfigs) { |
| optionCopy.areaIds.push_back(areaConfig.areaId); |
| } |
| } |
| |
| if (isGlobalProp(propId)) { |
| optionCopy.areaIds = {0}; |
| } |
| |
| if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) { |
| optionCopy.sampleRate = getDefaultSampleRateHz( |
| optionCopy.sampleRate, config.minSampleRate, config.maxSampleRate); |
| if (!optionCopy.enableVariableUpdateRate) { |
| continuousSubscriptions.push_back(std::move(optionCopy)); |
| } else { |
| // If clients enables to VUR, we need to check whether VUR is supported for the |
| // specific [propId, areaId] and overwrite the option to disable if not supported. |
| std::vector<int32_t> areasVurEnabled; |
| std::vector<int32_t> areasVurDisabled; |
| for (int32_t areaId : optionCopy.areaIds) { |
| const VehicleAreaConfig* areaConfig = getAreaConfig(propId, areaId, config); |
| if (areaConfig == nullptr) { |
| areasVurDisabled.push_back(areaId); |
| continue; |
| } |
| if (!areaConfig->supportVariableUpdateRate) { |
| areasVurDisabled.push_back(areaId); |
| continue; |
| } |
| areasVurEnabled.push_back(areaId); |
| } |
| if (!areasVurEnabled.empty()) { |
| SubscribeOptions optionVurEnabled = optionCopy; |
| optionVurEnabled.areaIds = areasVurEnabled; |
| optionVurEnabled.enableVariableUpdateRate = true; |
| continuousSubscriptions.push_back(std::move(optionVurEnabled)); |
| } |
| |
| if (!areasVurDisabled.empty()) { |
| // We use optionCopy for areas with VUR disabled. |
| optionCopy.areaIds = areasVurDisabled; |
| optionCopy.enableVariableUpdateRate = false; |
| continuousSubscriptions.push_back(std::move(optionCopy)); |
| } |
| } |
| } else { |
| onChangeSubscriptions.push_back(std::move(optionCopy)); |
| } |
| } |
| |
| { |
| // Lock to make sure onBinderDied would not be called concurrently. |
| std::scoped_lock lockGuard(mLock); |
| if (!monitorBinderLifeCycleLocked(callback->asBinder().get())) { |
| return ScopedAStatus::fromExceptionCodeWithMessage(EX_TRANSACTION_FAILED, |
| "client died"); |
| } |
| |
| if (!onChangeSubscriptions.empty()) { |
| auto result = mSubscriptionManager->subscribe(callback, onChangeSubscriptions, |
| /*isContinuousProperty=*/false); |
| if (!result.ok()) { |
| return toScopedAStatus(result); |
| } |
| } |
| if (!continuousSubscriptions.empty()) { |
| auto result = mSubscriptionManager->subscribe(callback, continuousSubscriptions, |
| /*isContinuousProperty=*/true); |
| if (!result.ok()) { |
| return toScopedAStatus(result); |
| } |
| } |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus DefaultVehicleHal::unsubscribe(const CallbackType& callback, |
| const std::vector<int32_t>& propIds) { |
| if (callback == nullptr) { |
| return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER); |
| } |
| return toScopedAStatus(mSubscriptionManager->unsubscribe(callback->asBinder().get(), propIds)); |
| } |
| |
| ScopedAStatus DefaultVehicleHal::returnSharedMemory(const CallbackType&, int64_t) { |
| // TODO(b/200737967): implement this. |
| return ScopedAStatus::ok(); |
| } |
| |
| IVehicleHardware* DefaultVehicleHal::getHardware() { |
| return mVehicleHardware.get(); |
| } |
| |
| VhalResult<void> DefaultVehicleHal::checkPermissionHelper( |
| const VehiclePropValue& value, VehiclePropertyAccess accessToTest) const { |
| static const std::unordered_set<VehiclePropertyAccess> validAccesses = { |
| VehiclePropertyAccess::WRITE, VehiclePropertyAccess::READ, |
| VehiclePropertyAccess::READ_WRITE}; |
| if (validAccesses.find(accessToTest) == validAccesses.end()) { |
| return StatusError(StatusCode::INVALID_ARG) |
| << "checkPermissionHelper parameter is an invalid access type"; |
| } |
| |
| int32_t propId = value.prop; |
| auto result = getConfig(propId); |
| if (!result.ok()) { |
| return StatusError(StatusCode::INVALID_ARG) << getErrorMsg(result); |
| } |
| const VehiclePropConfig* config = result.value(); |
| const VehicleAreaConfig* areaConfig = getAreaConfig(value, *config); |
| |
| if (areaConfig == nullptr && !isGlobalProp(propId)) { |
| return StatusError(StatusCode::INVALID_ARG) << "no config for area ID: " << value.areaId; |
| } |
| if (!hasRequiredAccess(config->access, accessToTest) && |
| (areaConfig == nullptr || !hasRequiredAccess(areaConfig->access, accessToTest))) { |
| return StatusError(StatusCode::ACCESS_DENIED) |
| << StringPrintf("Property %" PRId32 " does not have the following access: %" PRId32, |
| propId, accessToTest); |
| } |
| return {}; |
| } |
| |
| VhalResult<void> DefaultVehicleHal::checkWritePermission(const VehiclePropValue& value) const { |
| return checkPermissionHelper(value, VehiclePropertyAccess::WRITE); |
| } |
| |
| VhalResult<void> DefaultVehicleHal::checkReadPermission(const VehiclePropValue& value) const { |
| return checkPermissionHelper(value, VehiclePropertyAccess::READ); |
| } |
| |
| void DefaultVehicleHal::checkHealth(IVehicleHardware* vehicleHardware, |
| std::weak_ptr<SubscriptionManager> subscriptionManager) { |
| StatusCode status = vehicleHardware->checkHealth(); |
| if (status != StatusCode::OK) { |
| ALOGE("VHAL check health returns non-okay status"); |
| return; |
| } |
| std::vector<VehiclePropValue> values = {{ |
| .areaId = 0, |
| .prop = toInt(VehicleProperty::VHAL_HEARTBEAT), |
| .status = VehiclePropertyStatus::AVAILABLE, |
| .value.int64Values = {uptimeMillis()}, |
| }}; |
| onPropertyChangeEvent(subscriptionManager, std::move(values)); |
| return; |
| } |
| |
| binder_status_t DefaultVehicleHal::BinderLifecycleHandler::linkToDeath( |
| AIBinder* binder, AIBinder_DeathRecipient* recipient, void* cookie) { |
| return AIBinder_linkToDeath(binder, recipient, cookie); |
| } |
| |
| bool DefaultVehicleHal::BinderLifecycleHandler::isAlive(const AIBinder* binder) { |
| return AIBinder_isAlive(binder); |
| } |
| |
| void DefaultVehicleHal::setBinderLifecycleHandler( |
| std::unique_ptr<BinderLifecycleInterface> handler) { |
| mBinderLifecycleHandler = std::move(handler); |
| } |
| |
| bool DefaultVehicleHal::checkDumpPermission() { |
| uid_t uid = AIBinder_getCallingUid(); |
| return uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM; |
| } |
| |
| binder_status_t DefaultVehicleHal::dump(int fd, const char** args, uint32_t numArgs) { |
| if (!checkDumpPermission()) { |
| dprintf(fd, "Caller must be root, system or shell"); |
| return STATUS_PERMISSION_DENIED; |
| } |
| |
| std::vector<std::string> options; |
| for (uint32_t i = 0; i < numArgs; i++) { |
| options.push_back(args[i]); |
| } |
| if (options.size() == 1 && options[0] == "-a") { |
| // Ignore "-a" option. Bugreport will call with this option. |
| options.clear(); |
| } |
| DumpResult result = mVehicleHardware->dump(options); |
| if (result.refreshPropertyConfigs) { |
| getAllPropConfigsFromHardware(); |
| } |
| dprintf(fd, "%s", (result.buffer + "\n").c_str()); |
| if (!result.callerShouldDumpState) { |
| return STATUS_OK; |
| } |
| dprintf(fd, "Vehicle HAL State: \n"); |
| { |
| std::scoped_lock<std::mutex> lockGuard(mLock); |
| dprintf(fd, "Containing %zu property configs\n", mConfigsByPropId.size()); |
| dprintf(fd, "Currently have %zu getValues clients\n", mGetValuesClients.size()); |
| dprintf(fd, "Currently have %zu setValues clients\n", mSetValuesClients.size()); |
| dprintf(fd, "Currently have %zu subscribe clients\n", countSubscribeClients()); |
| } |
| return STATUS_OK; |
| } |
| |
| size_t DefaultVehicleHal::countSubscribeClients() { |
| return mSubscriptionManager->countClients(); |
| } |
| |
| } // namespace vehicle |
| } // namespace automotive |
| } // namespace hardware |
| } // namespace android |