| /* |
| * 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. |
| */ |
| |
| #include "generic_context_hub_aidl.h" |
| |
| #include "chre_api/chre/event.h" |
| #include "chre_host/config_util.h" |
| #include "chre_host/file_stream.h" |
| #include "chre_host/fragmented_load_transaction.h" |
| #include "chre_host/host_protocol_host.h" |
| #include "chre_host/log.h" |
| #include "chre_host/napp_header.h" |
| #include "permissions_util.h" |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <limits> |
| |
| namespace aidl::android::hardware::contexthub { |
| |
| // Aliased for consistency with the way these symbols are referenced in |
| // CHRE-side code |
| namespace fbs = ::chre::fbs; |
| |
| using ::android::chre::FragmentedLoadTransaction; |
| using ::android::chre::getPreloadedNanoappsFromConfigFile; |
| using ::android::chre::getStringFromByteVector; |
| using ::android::chre::NanoAppBinaryHeader; |
| using ::android::chre::readFileContents; |
| using ::android::hardware::contexthub::common::implementation:: |
| chreToAndroidPermissions; |
| using ::android::hardware::contexthub::common::implementation:: |
| kSupportedPermissions; |
| using ::ndk::ScopedAStatus; |
| |
| namespace { |
| constexpr uint32_t kDefaultHubId = 0; |
| constexpr char kPreloadedNanoappsConfigPath[] = |
| "/vendor/etc/chre/preloaded_nanoapps.json"; |
| constexpr std::chrono::duration kTestModeTimeout = std::chrono::seconds(10); |
| |
| /* |
| * The starting transaction ID for internal transactions. We choose |
| * the limit + 1 here as any client will only pass non-negative values up to the |
| * limit. The socket connection to CHRE accepts a uint32_t for the transaction |
| * ID, so we can use the value below up to std::numeric_limits<uint32_t>::max() |
| * for internal transaction IDs. |
| */ |
| constexpr int32_t kStartingInternalTransactionId = 0x80000000; |
| |
| inline constexpr int8_t extractChreApiMajorVersion(uint32_t chreVersion) { |
| return static_cast<int8_t>(chreVersion >> 24); |
| } |
| |
| inline constexpr int8_t extractChreApiMinorVersion(uint32_t chreVersion) { |
| return static_cast<int8_t>(chreVersion >> 16); |
| } |
| |
| inline constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) { |
| return static_cast<uint16_t>(chreVersion); |
| } |
| |
| bool getFbsSetting(const Setting &setting, fbs::Setting *fbsSetting) { |
| bool foundSetting = true; |
| |
| switch (setting) { |
| case Setting::LOCATION: |
| *fbsSetting = fbs::Setting::LOCATION; |
| break; |
| case Setting::AIRPLANE_MODE: |
| *fbsSetting = fbs::Setting::AIRPLANE_MODE; |
| break; |
| case Setting::MICROPHONE: |
| *fbsSetting = fbs::Setting::MICROPHONE; |
| break; |
| default: |
| foundSetting = false; |
| LOGE("Setting update with invalid enum value %hhu", setting); |
| break; |
| } |
| |
| return foundSetting; |
| } |
| |
| ScopedAStatus toServiceSpecificError(bool success) { |
| return success ? ScopedAStatus::ok() |
| : ScopedAStatus::fromServiceSpecificError( |
| BnContextHub::EX_CONTEXT_HUB_UNSPECIFIED); |
| } |
| |
| } // anonymous namespace |
| |
| ScopedAStatus ContextHub::getContextHubs( |
| std::vector<ContextHubInfo> *out_contextHubInfos) { |
| ::chre::fbs::HubInfoResponseT response; |
| bool success = mConnection.getContextHubs(&response); |
| if (success) { |
| ContextHubInfo hub; |
| hub.name = getStringFromByteVector(response.name); |
| hub.vendor = getStringFromByteVector(response.vendor); |
| hub.toolchain = getStringFromByteVector(response.toolchain); |
| hub.id = kDefaultHubId; |
| hub.peakMips = response.peak_mips; |
| hub.maxSupportedMessageLengthBytes = response.max_msg_len; |
| hub.chrePlatformId = response.platform_id; |
| |
| uint32_t version = response.chre_platform_version; |
| hub.chreApiMajorVersion = extractChreApiMajorVersion(version); |
| hub.chreApiMinorVersion = extractChreApiMinorVersion(version); |
| hub.chrePatchVersion = extractChrePatchVersion(version); |
| |
| hub.supportedPermissions = kSupportedPermissions; |
| |
| out_contextHubInfos->push_back(hub); |
| } |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::loadNanoapp(int32_t contextHubId, |
| const NanoappBinary &appBinary, |
| int32_t transactionId) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| |
| std::lock_guard<std::mutex> lock(mTestModeMutex); |
| bool success = loadNanoappInternal(appBinary, transactionId); |
| return toServiceSpecificError(success); |
| } |
| |
| ScopedAStatus ContextHub::unloadNanoapp(int32_t contextHubId, int64_t appId, |
| int32_t transactionId) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| |
| std::lock_guard<std::mutex> lock(mTestModeMutex); |
| bool success = unloadNanoappInternal(appId, transactionId); |
| return toServiceSpecificError(success); |
| } |
| |
| ScopedAStatus ContextHub::disableNanoapp(int32_t /* contextHubId */, |
| int64_t appId, |
| int32_t /* transactionId */) { |
| LOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported", |
| appId); |
| return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ScopedAStatus ContextHub::enableNanoapp(int32_t /* contextHubId */, |
| int64_t appId, |
| int32_t /* transactionId */) { |
| LOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported", appId); |
| return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| |
| ScopedAStatus ContextHub::onSettingChanged(Setting setting, bool enabled) { |
| mSettingEnabled[setting] = enabled; |
| fbs::Setting fbsSetting; |
| bool isWifiOrBtSetting = |
| (setting == Setting::WIFI_MAIN || setting == Setting::WIFI_SCANNING || |
| setting == Setting::BT_MAIN || setting == Setting::BT_SCANNING); |
| |
| if (!isWifiOrBtSetting && getFbsSetting(setting, &fbsSetting)) { |
| mConnection.sendSettingChangedNotification(fbsSetting, |
| toFbsSettingState(enabled)); |
| } |
| |
| bool isWifiMainEnabled = isSettingEnabled(Setting::WIFI_MAIN); |
| bool isWifiScanEnabled = isSettingEnabled(Setting::WIFI_SCANNING); |
| bool isAirplaneModeEnabled = isSettingEnabled(Setting::AIRPLANE_MODE); |
| |
| // Because the airplane mode impact on WiFi is not standardized in Android, |
| // we write a specific handling in the Context Hub HAL to inform CHRE. |
| // The following definition is a default one, and can be adjusted |
| // appropriately if necessary. |
| bool isWifiAvailable = isAirplaneModeEnabled |
| ? (isWifiMainEnabled) |
| : (isWifiMainEnabled || isWifiScanEnabled); |
| if (!mIsWifiAvailable.has_value() || (isWifiAvailable != mIsWifiAvailable)) { |
| mConnection.sendSettingChangedNotification( |
| fbs::Setting::WIFI_AVAILABLE, toFbsSettingState(isWifiAvailable)); |
| mIsWifiAvailable = isWifiAvailable; |
| } |
| |
| // The BT switches determine whether we can BLE scan which is why things are |
| // mapped like this into CHRE. |
| bool isBtMainEnabled = isSettingEnabled(Setting::BT_MAIN); |
| bool isBtScanEnabled = isSettingEnabled(Setting::BT_SCANNING); |
| bool isBleAvailable = isBtMainEnabled || isBtScanEnabled; |
| if (!mIsBleAvailable.has_value() || (isBleAvailable != mIsBleAvailable)) { |
| mConnection.sendSettingChangedNotification( |
| fbs::Setting::BLE_AVAILABLE, toFbsSettingState(isBleAvailable)); |
| mIsBleAvailable = isBleAvailable; |
| } |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::queryNanoapps(int32_t contextHubId) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| return toServiceSpecificError(mConnection.queryNanoapps()); |
| } |
| |
| ::ndk::ScopedAStatus ContextHub::getPreloadedNanoappIds( |
| int32_t contextHubId, std::vector<int64_t> *out_preloadedNanoappIds) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| |
| if (out_preloadedNanoappIds == nullptr) { |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| |
| std::unique_lock<std::mutex> lock(mPreloadedNanoappIdsMutex); |
| if (mPreloadedNanoappIds.has_value()) { |
| *out_preloadedNanoappIds = *mPreloadedNanoappIds; |
| return ScopedAStatus::ok(); |
| } |
| |
| std::vector<chrePreloadedNanoappInfo> preloadedNanoapps; |
| if (!getPreloadedNanoappIdsFromConfigFile(preloadedNanoapps, nullptr)) { |
| return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC); |
| } |
| |
| mPreloadedNanoappIds = std::vector<int64_t>(); |
| for (const auto &preloadedNanoapp : preloadedNanoapps) { |
| mPreloadedNanoappIds->push_back(preloadedNanoapp.id); |
| out_preloadedNanoappIds->push_back(preloadedNanoapp.id); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::registerCallback( |
| int32_t contextHubId, const std::shared_ptr<IContextHubCallback> &cb) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| if (mCallback != nullptr) { |
| binder_status_t binder_status = AIBinder_unlinkToDeath( |
| mCallback->asBinder().get(), mDeathRecipient.get(), this); |
| if (binder_status != STATUS_OK) { |
| LOGE("Failed to unlink to death"); |
| } |
| } |
| mCallback = cb; |
| if (cb != nullptr) { |
| binder_status_t binder_status = |
| AIBinder_linkToDeath(cb->asBinder().get(), mDeathRecipient.get(), this); |
| if (binder_status != STATUS_OK) { |
| LOGE("Failed to link to death"); |
| } |
| } |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::sendMessageToHub(int32_t contextHubId, |
| const ContextHubMessage &message) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| |
| bool success = mConnection.sendMessageToHub( |
| message.nanoappId, message.messageType, message.hostEndPoint, |
| message.messageBody.data(), message.messageBody.size()); |
| mEventLogger.logMessageToNanoapp(message, success); |
| |
| return toServiceSpecificError(success); |
| } |
| |
| ScopedAStatus ContextHub::setTestMode(bool enable) { |
| return enable ? enableTestMode() : disableTestMode(); |
| } |
| |
| ScopedAStatus ContextHub::onHostEndpointConnected( |
| const HostEndpointInfo &in_info) { |
| std::lock_guard<std::mutex> lock(mConnectedHostEndpointsMutex); |
| uint8_t type; |
| switch (in_info.type) { |
| case HostEndpointInfo::Type::APP: |
| type = CHRE_HOST_ENDPOINT_TYPE_APP; |
| break; |
| case HostEndpointInfo::Type::NATIVE: |
| type = CHRE_HOST_ENDPOINT_TYPE_NATIVE; |
| break; |
| case HostEndpointInfo::Type::FRAMEWORK: |
| type = CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK; |
| break; |
| default: |
| LOGE("Unsupported host endpoint type %" PRIu32, type); |
| return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); |
| } |
| mConnectedHostEndpoints.insert(in_info.hostEndpointId); |
| mConnection.onHostEndpointConnected( |
| in_info.hostEndpointId, type, in_info.packageName.value_or(std::string()), |
| in_info.attributionTag.value_or(std::string())); |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::onHostEndpointDisconnected( |
| char16_t in_hostEndpointId) { |
| std::lock_guard<std::mutex> lock(mConnectedHostEndpointsMutex); |
| if (mConnectedHostEndpoints.count(in_hostEndpointId) > 0) { |
| mConnectedHostEndpoints.erase(in_hostEndpointId); |
| |
| mConnection.onHostEndpointDisconnected(in_hostEndpointId); |
| } else { |
| LOGE("Unknown host endpoint disconnected (ID: %" PRIu16 ")", |
| in_hostEndpointId); |
| } |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus ContextHub::onNanSessionStateChanged( |
| const NanSessionStateUpdate & /*in_update*/) { |
| // TODO(271471342): Add support for NAN session management. |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| void ContextHub::onNanoappMessage(const ::chre::fbs::NanoappMessageT &message) { |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| if (mCallback != nullptr) { |
| mEventLogger.logMessageFromNanoapp(message); |
| ContextHubMessage outMessage; |
| outMessage.nanoappId = message.app_id; |
| outMessage.hostEndPoint = message.host_endpoint; |
| outMessage.messageType = message.message_type; |
| outMessage.messageBody = message.message; |
| outMessage.permissions = chreToAndroidPermissions(message.permissions); |
| |
| std::vector<std::string> messageContentPerms = |
| chreToAndroidPermissions(message.message_permissions); |
| mCallback->handleContextHubMessage(outMessage, messageContentPerms); |
| } |
| } |
| |
| void ContextHub::onNanoappListResponse( |
| const ::chre::fbs::NanoappListResponseT &response) { |
| std::vector<NanoappInfo> appInfoList; |
| for (const std::unique_ptr<::chre::fbs::NanoappListEntryT> &nanoapp : |
| response.nanoapps) { |
| // TODO(b/245202050): determine if this is really required, and if so, have |
| // HostProtocolHost strip out null entries as part of decode |
| if (nanoapp == nullptr) { |
| continue; |
| } |
| |
| LOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " permissions 0x%" PRIx32 |
| " enabled %d system %d", |
| nanoapp->app_id, nanoapp->version, nanoapp->permissions, |
| nanoapp->enabled, nanoapp->is_system); |
| if (!nanoapp->is_system) { |
| NanoappInfo appInfo; |
| |
| appInfo.nanoappId = nanoapp->app_id; |
| appInfo.nanoappVersion = nanoapp->version; |
| appInfo.enabled = nanoapp->enabled; |
| appInfo.permissions = chreToAndroidPermissions(nanoapp->permissions); |
| |
| std::vector<NanoappRpcService> rpcServices; |
| for (const auto &service : nanoapp->rpc_services) { |
| NanoappRpcService aidlService; |
| aidlService.id = service->id; |
| aidlService.version = service->version; |
| rpcServices.emplace_back(aidlService); |
| } |
| appInfo.rpcServices = rpcServices; |
| |
| appInfoList.push_back(appInfo); |
| } |
| } |
| |
| { |
| std::lock_guard<std::mutex> lock(mQueryNanoappsInternalMutex); |
| if (!mQueryNanoappsInternalList) { |
| mQueryNanoappsInternalList = appInfoList; |
| mQueryNanoappsInternalCondVar.notify_all(); |
| } |
| } |
| |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| if (mCallback != nullptr) { |
| mCallback->handleNanoappInfo(appInfoList); |
| } |
| } |
| |
| void ContextHub::onTransactionResult(uint32_t transactionId, bool success) { |
| std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex); |
| if (mSynchronousLoadUnloadTransactionId && |
| transactionId == *mSynchronousLoadUnloadTransactionId) { |
| mSynchronousLoadUnloadSuccess = success; |
| mSynchronousLoadUnloadCondVar.notify_all(); |
| } else { |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| if (mCallback != nullptr) { |
| mCallback->handleTransactionResult(transactionId, success); |
| } |
| } |
| } |
| |
| void ContextHub::onContextHubRestarted() { |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| mIsWifiAvailable.reset(); |
| { |
| std::lock_guard<std::mutex> endpointLock(mConnectedHostEndpointsMutex); |
| mConnectedHostEndpoints.clear(); |
| mEventLogger.logContextHubRestart(); |
| } |
| if (mCallback != nullptr) { |
| mCallback->handleContextHubAsyncEvent(AsyncEventType::RESTARTED); |
| } |
| } |
| |
| void ContextHub::onDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) { |
| auto str = std::string(reinterpret_cast<const char *>(data.debug_str.data()), |
| data.debug_str.size()); |
| debugDumpAppend(str); |
| } |
| |
| void ContextHub::onDebugDumpComplete( |
| const ::chre::fbs::DebugDumpResponseT & /* response */) { |
| debugDumpComplete(); |
| } |
| |
| void ContextHub::handleServiceDeath() { |
| LOGI("Context Hub Service died ..."); |
| { |
| std::lock_guard<std::mutex> lock(mCallbackMutex); |
| mCallback.reset(); |
| } |
| { |
| std::lock_guard<std::mutex> lock(mConnectedHostEndpointsMutex); |
| mConnectedHostEndpoints.clear(); |
| } |
| } |
| |
| void ContextHub::onServiceDied(void *cookie) { |
| auto *contexthub = static_cast<ContextHub *>(cookie); |
| contexthub->handleServiceDeath(); |
| } |
| |
| binder_status_t ContextHub::dump(int fd, const char ** /* args */, |
| uint32_t /* numArgs */) { |
| debugDumpStart(fd); |
| debugDumpFinish(); |
| return STATUS_OK; |
| } |
| |
| void ContextHub::debugDumpFinish() { |
| if (checkDebugFd()) { |
| const std::string &dump = mEventLogger.dump(); |
| writeToDebugFile(dump.c_str()); |
| writeToDebugFile("\n-- End of CHRE/ASH debug info --\n"); |
| invalidateDebugFd(); |
| } |
| } |
| |
| void ContextHub::writeToDebugFile(const char *str) { |
| if (!::android::base::WriteStringToFd(std::string(str), getDebugFd())) { |
| LOGW("Failed to write %zu bytes to debug dump fd", strlen(str)); |
| } |
| } |
| |
| ScopedAStatus ContextHub::enableTestMode() { |
| std::unique_lock<std::mutex> lock(mTestModeMutex); |
| |
| bool success = false; |
| std::vector<int64_t> loadedNanoappIds; |
| std::vector<int64_t> preloadedNanoappIds; |
| std::vector<int64_t> nanoappIdsToUnload; |
| if (mIsTestModeEnabled) { |
| success = true; |
| } else if (mConnection.isLoadTransactionPending()) { |
| /** |
| * There is already a pending load transaction. We cannot change the test |
| * mode state if there is a pending load transaction. We do not consider |
| * pending unload transactions as they can happen asynchronously and |
| * multiple at a time. |
| */ |
| LOGE("There exists a pending load transaction. Cannot enable test mode."); |
| } else if (!queryNanoappsInternal(kDefaultHubId, &loadedNanoappIds)) { |
| LOGE("Could not query nanoapps to enable test mode."); |
| } else if (!getPreloadedNanoappIds(kDefaultHubId, &preloadedNanoappIds) |
| .isOk()) { |
| LOGE("Unable to get preloaded nanoapp IDs from the config file."); |
| } else { |
| std::sort(loadedNanoappIds.begin(), loadedNanoappIds.end()); |
| std::sort(preloadedNanoappIds.begin(), preloadedNanoappIds.end()); |
| |
| // Calculate the system nanoapp IDs. They are preloaded, but not loaded. |
| mSystemNanoappIds.clear(); |
| std::set_difference(preloadedNanoappIds.begin(), preloadedNanoappIds.end(), |
| loadedNanoappIds.begin(), loadedNanoappIds.end(), |
| std::back_inserter(mSystemNanoappIds)); |
| |
| /* |
| * Unload all preloaded and loaded nanoapps (set intersection). |
| * Both vectors need to be sorted for std::set_intersection to work. |
| * We explicitly choose not to use std::set here to avoid the |
| * copying cost as well as the tree balancing cost for the |
| * red-black tree. |
| */ |
| std::set_intersection(loadedNanoappIds.begin(), loadedNanoappIds.end(), |
| preloadedNanoappIds.begin(), |
| preloadedNanoappIds.end(), |
| std::back_inserter(nanoappIdsToUnload)); |
| if (!unloadNanoappsInternal(kDefaultHubId, nanoappIdsToUnload)) { |
| LOGE("Unable to unload all loaded and preloaded nanoapps."); |
| } else { |
| success = true; |
| } |
| } |
| |
| if (success) { |
| mIsTestModeEnabled = true; |
| LOGI("Successfully enabled test mode."); |
| return ScopedAStatus::ok(); |
| } else { |
| return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC); |
| } |
| } |
| |
| ScopedAStatus ContextHub::disableTestMode() { |
| std::unique_lock<std::mutex> lock(mTestModeMutex); |
| |
| bool success = false; |
| std::vector<chrePreloadedNanoappInfo> preloadedNanoapps; |
| std::string preloadedNanoappDirectory; |
| if (!mIsTestModeEnabled) { |
| success = true; |
| } else if (mConnection.isLoadTransactionPending()) { |
| /** |
| * There is already a pending load transaction. We cannot change the test |
| * mode state if there is a pending load transaction. We do not consider |
| * pending unload transactions as they can happen asynchronously and |
| * multiple at a time. |
| */ |
| LOGE("There exists a pending load transaction. Cannot disable test mode."); |
| } else if (!getPreloadedNanoappIdsFromConfigFile( |
| preloadedNanoapps, &preloadedNanoappDirectory)) { |
| LOGE("Unable to get preloaded nanoapp IDs from the config file."); |
| } else { |
| std::vector<NanoappBinary> nanoappsToLoad = selectPreloadedNanoappsToLoad( |
| preloadedNanoapps, preloadedNanoappDirectory); |
| |
| if (!loadNanoappsInternal(kDefaultHubId, nanoappsToLoad)) { |
| LOGE("Unable to load all preloaded, non-system nanoapps."); |
| } else { |
| success = true; |
| } |
| } |
| |
| if (success) { |
| mIsTestModeEnabled = false; |
| LOGI("Successfully disabled test mode."); |
| return ScopedAStatus::ok(); |
| } else { |
| return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC); |
| } |
| } |
| |
| bool ContextHub::queryNanoappsInternal(int32_t contextHubId, |
| std::vector<int64_t> *nanoappIdList) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return false; |
| } |
| |
| std::unique_lock<std::mutex> lock(mQueryNanoappsInternalMutex); |
| mQueryNanoappsInternalList.reset(); |
| |
| bool success = |
| queryNanoapps(contextHubId).isOk() && |
| mQueryNanoappsInternalCondVar.wait_for(lock, kTestModeTimeout, [this]() { |
| return mQueryNanoappsInternalList.has_value(); |
| }); |
| if (success && nanoappIdList != nullptr) { |
| std::transform( |
| mQueryNanoappsInternalList->begin(), mQueryNanoappsInternalList->end(), |
| std::back_inserter(*nanoappIdList), |
| [](const NanoappInfo &nanoapp) { return nanoapp.nanoappId; }); |
| } |
| return success; |
| } |
| |
| bool ContextHub::loadNanoappInternal(const NanoappBinary &appBinary, |
| int32_t transactionId) { |
| uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) | |
| (appBinary.targetChreApiMinorVersion << 16); |
| FragmentedLoadTransaction transaction( |
| transactionId, appBinary.nanoappId, appBinary.nanoappVersion, |
| appBinary.flags, targetApiVersion, appBinary.customBinary); |
| bool success = mConnection.loadNanoapp(transaction); |
| mEventLogger.logNanoappLoad(appBinary, success); |
| return success; |
| } |
| |
| bool ContextHub::loadNanoappsInternal( |
| int32_t contextHubId, const std::vector<NanoappBinary> &nanoappBinaryList) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return false; |
| } |
| |
| std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex); |
| mSynchronousLoadUnloadTransactionId = kStartingInternalTransactionId; |
| |
| for (const NanoappBinary &nanoappToLoad : nanoappBinaryList) { |
| LOGI("Loading nanoapp with ID: 0x%016" PRIx64, nanoappToLoad.nanoappId); |
| |
| bool success = false; |
| if (!loadNanoappInternal(nanoappToLoad, |
| *mSynchronousLoadUnloadTransactionId)) { |
| LOGE("Failed to request loading nanoapp with ID 0x%" PRIx64, |
| nanoappToLoad.nanoappId); |
| } else { |
| mSynchronousLoadUnloadSuccess.reset(); |
| mSynchronousLoadUnloadCondVar.wait_for(lock, kTestModeTimeout, [this]() { |
| return mSynchronousLoadUnloadSuccess.has_value(); |
| }); |
| if (mSynchronousLoadUnloadSuccess.has_value() && |
| *mSynchronousLoadUnloadSuccess) { |
| LOGI("Successfully loaded nanoapp with ID: 0x%016" PRIx64, |
| nanoappToLoad.nanoappId); |
| ++(*mSynchronousLoadUnloadTransactionId); |
| success = true; |
| } |
| } |
| |
| if (!success) { |
| LOGE("Failed to load nanoapp with ID 0x%" PRIx64, |
| nanoappToLoad.nanoappId); |
| mSynchronousLoadUnloadTransactionId.reset(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ContextHub::unloadNanoappInternal(int64_t appId, int32_t transactionId) { |
| bool success = mConnection.unloadNanoapp(appId, transactionId); |
| mEventLogger.logNanoappUnload(appId, success); |
| return success; |
| } |
| |
| bool ContextHub::unloadNanoappsInternal( |
| int32_t contextHubId, const std::vector<int64_t> &nanoappIdList) { |
| if (contextHubId != kDefaultHubId) { |
| LOGE("Invalid ID %" PRId32, contextHubId); |
| return false; |
| } |
| |
| std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex); |
| mSynchronousLoadUnloadTransactionId = kStartingInternalTransactionId; |
| |
| for (int64_t nanoappIdToUnload : nanoappIdList) { |
| LOGI("Unloading nanoapp with ID: 0x%016" PRIx64, nanoappIdToUnload); |
| |
| bool success = false; |
| if (!unloadNanoappInternal(nanoappIdToUnload, |
| *mSynchronousLoadUnloadTransactionId)) { |
| LOGE("Failed to request unloading nanoapp with ID 0x%" PRIx64, |
| nanoappIdToUnload); |
| } else { |
| mSynchronousLoadUnloadSuccess.reset(); |
| mSynchronousLoadUnloadCondVar.wait_for(lock, kTestModeTimeout, [this]() { |
| return mSynchronousLoadUnloadSuccess.has_value(); |
| }); |
| if (mSynchronousLoadUnloadSuccess.has_value() && |
| *mSynchronousLoadUnloadSuccess) { |
| LOGI("Successfully unloaded nanoapp with ID: 0x%016" PRIx64, |
| nanoappIdToUnload); |
| ++(*mSynchronousLoadUnloadTransactionId); |
| success = true; |
| } |
| } |
| |
| if (!success) { |
| LOGE("Failed to unload nanoapp with ID 0x%" PRIx64, nanoappIdToUnload); |
| mSynchronousLoadUnloadTransactionId.reset(); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ContextHub::getPreloadedNanoappIdsFromConfigFile( |
| std::vector<chrePreloadedNanoappInfo> &out_preloadedNanoapps, |
| std::string *out_directory) const { |
| std::vector<std::string> nanoappNames; |
| std::string directory; |
| |
| bool success = getPreloadedNanoappsFromConfigFile( |
| kPreloadedNanoappsConfigPath, directory, nanoappNames); |
| if (!success) { |
| LOGE("Failed to parse preloaded nanoapps config file"); |
| } |
| |
| for (const std::string &nanoappName : nanoappNames) { |
| std::string headerFile = directory + "/" + nanoappName + ".napp_header"; |
| std::vector<uint8_t> headerBuffer; |
| if (!readFileContents(headerFile.c_str(), headerBuffer)) { |
| LOGE("Cannot read header file: %s", headerFile.c_str()); |
| continue; |
| } |
| |
| if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) { |
| LOGE("Header size mismatch"); |
| continue; |
| } |
| |
| const auto *appHeader = |
| reinterpret_cast<const NanoAppBinaryHeader *>(headerBuffer.data()); |
| out_preloadedNanoapps.emplace_back(static_cast<int64_t>(appHeader->appId), |
| nanoappName, *appHeader); |
| } |
| |
| if (out_directory != nullptr) { |
| *out_directory = directory; |
| } |
| |
| return true; |
| } |
| |
| std::vector<NanoappBinary> ContextHub::selectPreloadedNanoappsToLoad( |
| std::vector<chrePreloadedNanoappInfo> &preloadedNanoapps, |
| const std::string &preloadedNanoappDirectory) { |
| std::vector<NanoappBinary> nanoappsToLoad; |
| |
| for (auto &preloadedNanoapp : preloadedNanoapps) { |
| int64_t nanoappId = preloadedNanoapp.id; |
| |
| // A nanoapp is a system nanoapp if it is in the preloaded nanoapp list |
| // but not in the loaded nanoapp list as CHRE hides system nanoapps |
| // from the HAL. |
| bool isSystemNanoapp = |
| std::any_of(mSystemNanoappIds.begin(), mSystemNanoappIds.end(), |
| [nanoappId](int64_t systemNanoappId) { |
| return systemNanoappId == nanoappId; |
| }); |
| if (!isSystemNanoapp) { |
| std::vector<uint8_t> nanoappBuffer; |
| std::string nanoappFile = |
| preloadedNanoappDirectory + "/" + preloadedNanoapp.name + ".so"; |
| if (!readFileContents(nanoappFile.c_str(), nanoappBuffer)) { |
| LOGE("Cannot read header file: %s", nanoappFile.c_str()); |
| } else { |
| NanoappBinary nanoapp; |
| nanoapp.nanoappId = preloadedNanoapp.header.appId; |
| nanoapp.nanoappVersion = preloadedNanoapp.header.appVersion; |
| nanoapp.flags = preloadedNanoapp.header.flags; |
| nanoapp.targetChreApiMajorVersion = |
| preloadedNanoapp.header.targetChreApiMajorVersion; |
| nanoapp.targetChreApiMinorVersion = |
| preloadedNanoapp.header.targetChreApiMinorVersion; |
| nanoapp.customBinary = nanoappBuffer; |
| |
| nanoappsToLoad.push_back(nanoapp); |
| } |
| } |
| } |
| return nanoappsToLoad; |
| } |
| |
| } // namespace aidl::android::hardware::contexthub |