/* * Copyright (C) 2018 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 "chre/core/gnss_manager.h" #include #include "chre/core/event_loop_manager.h" #include "chre/core/settings.h" #include "chre/platform/assert.h" #include "chre/platform/fatal_error.h" #include "chre/util/nested_data_ptr.h" #include "chre/util/system/debug_dump.h" #include "chre/util/system/event_callbacks.h" namespace chre { namespace { bool getCallbackType(uint16_t eventType, SystemCallbackType *callbackType) { bool success = true; switch (eventType) { case CHRE_EVENT_GNSS_LOCATION: { *callbackType = SystemCallbackType::GnssLocationReportEvent; break; } case CHRE_EVENT_GNSS_DATA: { *callbackType = SystemCallbackType::GnssMeasurementReportEvent; break; } default: { LOGE("Unknown event type %" PRIu16, eventType); success = false; } } return success; } bool getReportEventType(SystemCallbackType callbackType, uint16_t *eventType) { bool success = true; switch (callbackType) { case SystemCallbackType::GnssLocationReportEvent: { *eventType = CHRE_EVENT_GNSS_LOCATION; break; } case SystemCallbackType::GnssMeasurementReportEvent: { *eventType = CHRE_EVENT_GNSS_DATA; break; } default: { LOGE("Unknown callback type %" PRIu16, static_cast(callbackType)); success = false; } } return success; } } // anonymous namespace GnssManager::GnssManager() : mLocationSession(CHRE_EVENT_GNSS_LOCATION), mMeasurementSession(CHRE_EVENT_GNSS_DATA) {} void GnssManager::init() { mPlatformGnss.init(); } uint32_t GnssManager::getCapabilities() { return mPlatformGnss.getCapabilities(); } void GnssManager::onSettingChanged(Setting setting, bool enabled) { mLocationSession.onSettingChanged(setting, enabled); mMeasurementSession.onSettingChanged(setting, enabled); } void GnssManager::handleRequestStateResyncCallback() { auto callback = [](uint16_t /* eventType */, void * /* eventData */, void * /* extraData */) { EventLoopManagerSingleton::get() ->getGnssManager() .handleRequestStateResyncCallbackSync(); }; EventLoopManagerSingleton::get()->deferCallback( SystemCallbackType::GnssRequestResyncEvent, nullptr /* data */, callback); } bool GnssManager::configurePassiveLocationListener(Nanoapp *nanoapp, bool enable) { bool success = false; uint16_t instanceId = nanoapp->getInstanceId(); size_t index; if (nanoappHasPassiveLocationListener(instanceId, &index) != enable) { uint32_t capabilities = getCapabilities(); bool locationSupported = (capabilities & CHRE_GNSS_CAPABILITIES_LOCATION) != 0; bool passiveLocationListenerSupported = (capabilities & CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER) != 0; if (!locationSupported) { LOGE("Platform does not have the location capability"); } else if (enable && !mPassiveLocationListenerNanoapps.prepareForPush()) { LOG_OOM(); } else { bool platformEnable = enable && mPassiveLocationListenerNanoapps.empty(); bool platformDisable = !enable && (mPassiveLocationListenerNanoapps.size() == 1); if (!passiveLocationListenerSupported) { // Silently succeed per API, since listener capability will occur within // CHRE (nanoapp requests). success = true; } else if (platformEnable || platformDisable) { success = platformConfigurePassiveLocationListener(enable); } else { // Platform was already in the configured state. success = true; } if (success) { if (enable) { mPassiveLocationListenerNanoapps.push_back(instanceId); nanoapp->registerForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); } else { mPassiveLocationListenerNanoapps.erase(index); if (!mLocationSession.nanoappHasRequest(instanceId)) { nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); } } } } } else { // else nanoapp request is already at the desired state. success = true; } return success; } bool GnssManager::nanoappHasPassiveLocationListener(uint16_t nanoappInstanceId, size_t *index) { size_t foundIndex = mPassiveLocationListenerNanoapps.find(nanoappInstanceId); bool found = (foundIndex != mPassiveLocationListenerNanoapps.size()); if (found && index != nullptr) { *index = foundIndex; } return found; } bool GnssManager::platformConfigurePassiveLocationListener(bool enable) { bool success = mPlatformGnss.configurePassiveLocationListener(enable); if (!success) { LOGE("Platform failed to %s passive location listener", enable ? "enable" : "disable"); } else { mPlatformPassiveLocationListenerEnabled = enable; } return success; } void GnssManager::handleRequestStateResyncCallbackSync() { mLocationSession.handleRequestStateResyncCallbackSync(); mMeasurementSession.handleRequestStateResyncCallbackSync(); mPlatformPassiveLocationListenerEnabled = false; if (!mPassiveLocationListenerNanoapps.empty()) { if (!platformConfigurePassiveLocationListener(true /* enable */)) { FATAL_ERROR("Failed to resync passive location listener"); } } } void GnssManager::logStateToBuffer(DebugDumpWrapper &debugDump) const { debugDump.print("\nGNSS:"); mLocationSession.logStateToBuffer(debugDump); mMeasurementSession.logStateToBuffer(debugDump); debugDump.print("\n API error distribution (error-code indexed):\n"); debugDump.print(" GNSS Location:\n"); debugDump.logErrorHistogram(mLocationSession.mGnssErrorHistogram, ARRAY_SIZE(mLocationSession.mGnssErrorHistogram)); debugDump.print(" GNSS Measurement:\n"); debugDump.logErrorHistogram( mMeasurementSession.mGnssErrorHistogram, ARRAY_SIZE(mMeasurementSession.mGnssErrorHistogram)); debugDump.print( "\n Passive location listener %s\n", mPlatformPassiveLocationListenerEnabled ? "enabled" : "disabled"); for (uint16_t instanceId : mPassiveLocationListenerNanoapps) { debugDump.print(" nappId=%" PRIu16 "\n", instanceId); } } uint32_t GnssManager::disableAllSubscriptions(Nanoapp *nanoapp) { uint32_t numDisabledSubscriptions = 0; size_t index; if (mLocationSession.nanoappHasRequest(nanoapp)) { numDisabledSubscriptions++; mLocationSession.removeRequest(nanoapp, nullptr /*cookie*/); } if (mMeasurementSession.nanoappHasRequest(nanoapp)) { numDisabledSubscriptions++; mMeasurementSession.removeRequest(nanoapp, nullptr /*cookie*/); } if (nanoappHasPassiveLocationListener(nanoapp->getInstanceId(), &index)) { numDisabledSubscriptions++; configurePassiveLocationListener(nanoapp, false /*enable*/); } return numDisabledSubscriptions; } GnssSession::GnssSession(uint16_t reportEventType) : kReportEventType(reportEventType) { switch (kReportEventType) { case CHRE_EVENT_GNSS_LOCATION: mStartRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START; mStopRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP; mName = "Location"; break; case CHRE_EVENT_GNSS_DATA: mStartRequestType = CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_START; mStopRequestType = CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_STOP; mName = "Measurement"; break; default: CHRE_ASSERT_LOG(false, "Unsupported eventType %" PRIu16, reportEventType); } if (!mRequests.reserve(1)) { FATAL_ERROR_OOM(); } } bool GnssSession::addRequest(Nanoapp *nanoapp, Milliseconds minInterval, Milliseconds minTimeToNext, const void *cookie) { CHRE_ASSERT(nanoapp); return configure(nanoapp, true /* enable */, minInterval, minTimeToNext, cookie); } bool GnssSession::removeRequest(Nanoapp *nanoapp, const void *cookie) { CHRE_ASSERT(nanoapp); return configure(nanoapp, false /* enable */, Milliseconds(UINT64_MAX), Milliseconds(UINT64_MAX), cookie); } void GnssSession::handleStatusChange(bool enabled, uint8_t errorCode) { struct CallbackState { bool enabled; uint8_t errorCode; }; auto callback = [](uint16_t /*type*/, void *data, void *extraData) { auto *session = static_cast(data); CallbackState cbState = NestedDataPtr(extraData); session->handleStatusChangeSync(cbState.enabled, cbState.errorCode); }; CallbackState cbState = {}; cbState.enabled = enabled; cbState.errorCode = errorCode; EventLoopManagerSingleton::get()->deferCallback( SystemCallbackType::GnssSessionStatusChange, /*data=*/this, callback, NestedDataPtr(cbState)); } void GnssSession::handleReportEvent(void *event) { if (mRequests.empty()) { LOGW("Unexpected %s event", mName); } auto callback = [](uint16_t type, void *data, void * /*extraData*/) { uint16_t reportEventType = 0; if (!getReportEventType(static_cast(type), &reportEventType) || !EventLoopManagerSingleton::get() ->getSettingManager() .getSettingEnabled(Setting::LOCATION)) { freeReportEventCallback(reportEventType, data); } else { EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie( reportEventType, data, freeReportEventCallback); } }; SystemCallbackType type; if (!getCallbackType(kReportEventType, &type) || !EventLoopManagerSingleton::get()->deferCallback(type, event, callback)) { freeReportEventCallback(kReportEventType, event); } } void GnssSession::onSettingChanged(Setting setting, bool /*enabled*/) { if (setting == Setting::LOCATION) { if (asyncResponsePending()) { // A request is in progress, so we wait until the async response arrives // to handle the state change. mSettingChangePending = true; } else { mInternalRequestPending = updatePlatformRequest(); mSettingChangePending = false; } } } bool GnssSession::updatePlatformRequest(bool forceUpdate) { bool enabled = EventLoopManagerSingleton::get()->getSettingManager().getSettingEnabled( Setting::LOCATION); bool desiredPlatformState = enabled && !mRequests.empty(); bool shouldUpdatePlatform = forceUpdate || (desiredPlatformState != mPlatformEnabled) /* (enable/disable) */; bool requestPending = false; if (shouldUpdatePlatform) { if (controlPlatform(desiredPlatformState, mCurrentInterval, Milliseconds(0) /* minTimeToNext */)) { LOGD("Configured GNSS %s: enable %d", mName, desiredPlatformState); addSessionRequestLog(CHRE_INSTANCE_ID, mCurrentInterval, desiredPlatformState); requestPending = true; } else { LOGE("Failed to configure GNSS %s: enable %d", mName, desiredPlatformState); } } return requestPending; } void GnssSession::handleRequestStateResyncCallbackSync() { if (asyncResponsePending()) { // A request is in progress, so we wait until the async response arrives // to handle the resync callback. mResyncPending = true; } else { mInternalRequestPending = updatePlatformRequest(true /* forceUpdate */); } } void GnssSession::logStateToBuffer(DebugDumpWrapper &debugDump) const { // TODO: have all interval values print as INVALID if they are the max // unsigned value debugDump.print("\n %s: Curr int(ms)=%" PRIu64 "\n", mName, mCurrentInterval.getMilliseconds()); debugDump.print(" Requests:\n"); for (const auto &request : mRequests) { debugDump.print(" minInt(ms)=%" PRIu64 " nappId=%" PRIu32 "\n", request.minInterval.getMilliseconds(), request.nanoappInstanceId); } if (!mStateTransitions.empty()) { debugDump.print(" Transition queue:\n"); for (const auto &transition : mStateTransitions) { debugDump.print(" minInt(ms)=%" PRIu64 " enable=%d nappId=%" PRIu16 "\n", transition.minInterval.getMilliseconds(), transition.enable, transition.nanoappInstanceId); } } debugDump.print(" Last %zu session requests:\n", mSessionRequestLogs.size()); static_assert(kNumSessionRequestLogs <= INT8_MAX, "kNumSessionRequestLogs must be less than INT8_MAX."); for (int8_t i = static_cast(mSessionRequestLogs.size()) - 1; i >= 0; i--) { const auto &log = mSessionRequestLogs[static_cast(i)]; debugDump.print(" ts=%" PRIu64 " nappId=%" PRIu16 " %s", log.timestamp.toRawNanoseconds(), log.instanceId, log.start ? "start" : "stop\n"); if (log.start) { debugDump.print(" int(ms)=%" PRIu64 "\n", log.interval.getMilliseconds()); } } } bool GnssSession::configure(Nanoapp *nanoapp, bool enable, Milliseconds minInterval, Milliseconds minTimeToNext, const void *cookie) { bool success = false; uint16_t instanceId = nanoapp->getInstanceId(); size_t requestIndex = 0; bool hasRequest = nanoappHasRequest(instanceId, &requestIndex); if (asyncResponsePending()) { success = addRequestToQueue(instanceId, enable, minInterval, cookie); } else if (stateTransitionIsRequired(enable, minInterval, hasRequest, requestIndex)) { if (enable && !EventLoopManagerSingleton::get() ->getSettingManager() .getSettingEnabled(Setting::LOCATION)) { // Treat as success but post async failure per API. success = postAsyncResultEvent(instanceId, false /* success */, enable, minInterval, CHRE_ERROR_FUNCTION_DISABLED, cookie); } else if (addRequestToQueue(instanceId, enable, minInterval, cookie)) { success = controlPlatform(enable, minInterval, minTimeToNext); if (!success) { mStateTransitions.pop_back(); LOGE("Failed to request a GNSS session for nanoapp instance %" PRIu16 " enable %d", instanceId, enable); } } } else { success = postAsyncResultEvent(instanceId, true /* success */, enable, minInterval, CHRE_ERROR_NONE, cookie); } if (success) { addSessionRequestLog(nanoapp->getInstanceId(), minInterval, enable); } return success; } bool GnssSession::nanoappHasRequest(uint16_t instanceId, size_t *requestIndex) const { bool hasRequest = false; for (size_t i = 0; i < mRequests.size(); i++) { if (mRequests[i].nanoappInstanceId == instanceId) { hasRequest = true; if (requestIndex != nullptr) { *requestIndex = i; } break; } } return hasRequest; } bool GnssSession::nanoappHasRequest(Nanoapp *nanoapp) const { return nanoappHasRequest(nanoapp->getInstanceId(), nullptr /*requestIndex*/); } bool GnssSession::addRequestToQueue(uint16_t instanceId, bool enable, Milliseconds minInterval, const void *cookie) { StateTransition stateTransition; stateTransition.nanoappInstanceId = instanceId; stateTransition.enable = enable; stateTransition.minInterval = minInterval; stateTransition.cookie = cookie; bool success = mStateTransitions.push(stateTransition); if (!success) { LOGW("Too many session state transitions"); } return success; } bool GnssSession::isEnabled() const { return !mRequests.empty(); } bool GnssSession::stateTransitionIsRequired(bool requestedState, Milliseconds minInterval, bool nanoappHasRequest, size_t requestIndex) const { bool requestToEnable = (requestedState && !isEnabled()); bool requestToIncreaseRate = (requestedState && isEnabled() && minInterval < mCurrentInterval); bool requestToDisable = (!requestedState && nanoappHasRequest && mRequests.size() == 1); // An effective rate decrease for the session can only occur if the nanoapp // has an existing request. bool requestToDecreaseRate = false; if (nanoappHasRequest) { // The nanoapp has an existing request. Check that the request does not // result in a rate decrease by checking if no other nanoapps have the // same request, the nanoapp's existing request is not equal to the current // requested interval and the new request is slower than the current // requested rate. size_t requestCount = 0; const auto ¤tRequest = mRequests[requestIndex]; for (size_t i = 0; i < mRequests.size(); i++) { const Request &request = mRequests[i]; if (i != requestIndex && request.minInterval == currentRequest.minInterval) { requestCount++; } } requestToDecreaseRate = (minInterval > mCurrentInterval && currentRequest.minInterval == mCurrentInterval && requestCount == 0); } return (requestToEnable || requestToDisable || requestToIncreaseRate || requestToDecreaseRate); } bool GnssSession::updateRequests(bool enable, Milliseconds minInterval, uint16_t instanceId) { bool success = true; Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId( instanceId); if (nanoapp == nullptr) { LOGW("Failed to update GNSS session request list for non-existent nanoapp"); } else { size_t requestIndex; bool hasExistingRequest = nanoappHasRequest(instanceId, &requestIndex); if (enable) { if (hasExistingRequest) { // If the nanoapp has an open request ensure that the minInterval is // kept up to date. mRequests[requestIndex].minInterval = minInterval; } else { // The GNSS session was successfully enabled for this nanoapp and // there is no existing request. Add it to the list of GNSS session // nanoapps. Request request; request.nanoappInstanceId = instanceId; request.minInterval = minInterval; success = mRequests.push_back(request); if (!success) { LOG_OOM(); } else { nanoapp->registerForBroadcastEvent(kReportEventType); } } } else if (hasExistingRequest) { // The session was successfully disabled for a previously enabled // nanoapp. Remove it from the list of requests. mRequests.erase(requestIndex); // We can only unregister the location events from nanoapps if it has no // request and has not configured the passive listener. if ((kReportEventType != CHRE_EVENT_GNSS_LOCATION) || !EventLoopManagerSingleton::get() ->getGnssManager() .nanoappHasPassiveLocationListener(instanceId)) { nanoapp->unregisterForBroadcastEvent(kReportEventType); } } // else disabling an inactive request, treat as success per CHRE API } return success; } bool GnssSession::postAsyncResultEvent(uint16_t instanceId, bool success, bool enable, Milliseconds minInterval, uint8_t errorCode, const void *cookie) { bool eventPosted = false; if (!success || updateRequests(enable, minInterval, instanceId)) { chreAsyncResult *event = memoryAlloc(); if (event == nullptr) { LOG_OOM(); } else { event->requestType = enable ? mStartRequestType : mStopRequestType; event->success = success; event->errorCode = errorCode; event->reserved = 0; event->cookie = cookie; mGnssErrorHistogram[errorCode]++; EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie( CHRE_EVENT_GNSS_ASYNC_RESULT, event, freeEventDataCallback, instanceId); eventPosted = true; } } return eventPosted; } void GnssSession::postAsyncResultEventFatal(uint16_t instanceId, bool success, bool enable, Milliseconds minInterval, uint8_t errorCode, const void *cookie) { if (!postAsyncResultEvent(instanceId, success, enable, minInterval, errorCode, cookie)) { FATAL_ERROR("Failed to send GNSS session request async result event"); } } void GnssSession::handleStatusChangeSync(bool enabled, uint8_t errorCode) { bool success = (errorCode == CHRE_ERROR_NONE); if (mInternalRequestPending) { // Silently handle internal requests from CHRE, since they are not pushed // to the mStateTransitions queue. mInternalRequestPending = false; } else if (!mStateTransitions.empty()) { const auto &stateTransition = mStateTransitions.front(); if (success) { mCurrentInterval = stateTransition.minInterval; } if (success && stateTransition.enable != enabled) { success = false; errorCode = CHRE_ERROR; LOGE("GNSS PAL did not transition to expected state"); } postAsyncResultEventFatal( stateTransition.nanoappInstanceId, success, stateTransition.enable, stateTransition.minInterval, errorCode, stateTransition.cookie); mStateTransitions.pop(); } else { // TODO(b/296222493): change this back to an assert once issue resolved LOGE("GnssSession::handleStatusChangeSync called with no transitions"); return; } // If a previous setting change or resync event is pending process, do that // first. if (mResyncPending && !success) { // We only send a platform request on resync if a pending request failed, // because we still need to restore the previous request state. mInternalRequestPending = updatePlatformRequest(true /* forceUpdate */); } else if (mSettingChangePending) { mInternalRequestPending = updatePlatformRequest(); } mResyncPending = false; mSettingChangePending = false; // If we didn't issue an internally-generated update via // updatePlatformRequest(), process pending nanoapp requests (otherwise, // wait for it to finish, then process any pending requests) if (!mInternalRequestPending) { dispatchQueuedStateTransitions(); } } void GnssSession::freeReportEventCallback(uint16_t eventType, void *eventData) { switch (eventType) { case CHRE_EVENT_GNSS_LOCATION: EventLoopManagerSingleton::get() ->getGnssManager() .mPlatformGnss.releaseLocationEvent( static_cast(eventData)); break; case CHRE_EVENT_GNSS_DATA: EventLoopManagerSingleton::get() ->getGnssManager() .mPlatformGnss.releaseMeasurementDataEvent( static_cast(eventData)); break; default: CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, eventType); } } bool GnssSession::controlPlatform(bool enable, Milliseconds minInterval, Milliseconds /* minTimeToNext */) { bool success = false; switch (kReportEventType) { case CHRE_EVENT_GNSS_LOCATION: // TODO: Provide support for min time to next report. It is currently sent // to the platform as zero. success = EventLoopManagerSingleton::get() ->getGnssManager() .mPlatformGnss.controlLocationSession(enable, minInterval, Milliseconds(0)); break; case CHRE_EVENT_GNSS_DATA: success = EventLoopManagerSingleton::get() ->getGnssManager() .mPlatformGnss.controlMeasurementSession(enable, minInterval); break; default: CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, kReportEventType); } if (success) { mPlatformEnabled = enable; } return success; } void GnssSession::addSessionRequestLog(uint16_t nanoappInstanceId, Milliseconds interval, bool start) { mSessionRequestLogs.kick_push(SessionRequestLog( SystemTime::getMonotonicTime(), nanoappInstanceId, interval, start)); } void GnssSession::dispatchQueuedStateTransitions() { while (!mStateTransitions.empty()) { const auto &stateTransition = mStateTransitions.front(); size_t requestIndex = 0; bool hasRequest = nanoappHasRequest(stateTransition.nanoappInstanceId, &requestIndex); if (stateTransitionIsRequired(stateTransition.enable, stateTransition.minInterval, hasRequest, requestIndex)) { if (!EventLoopManagerSingleton::get() ->getSettingManager() .getSettingEnabled(Setting::LOCATION)) { postAsyncResultEventFatal( stateTransition.nanoappInstanceId, false /* success */, stateTransition.enable, stateTransition.minInterval, CHRE_ERROR_FUNCTION_DISABLED, stateTransition.cookie); mStateTransitions.pop(); } else if (controlPlatform(stateTransition.enable, stateTransition.minInterval, Milliseconds(0))) { break; } else { LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu16, stateTransition.nanoappInstanceId); postAsyncResultEventFatal(stateTransition.nanoappInstanceId, false /* success */, stateTransition.enable, stateTransition.minInterval, CHRE_ERROR, stateTransition.cookie); mStateTransitions.pop(); } } else { postAsyncResultEventFatal(stateTransition.nanoappInstanceId, true /* success */, stateTransition.enable, stateTransition.minInterval, CHRE_ERROR_NONE, stateTransition.cookie); mStateTransitions.pop(); } } } } // namespace chre