/* * Copyright (C) 2017 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_request_manager.h" #include "chre/core/event_loop_manager.h" #include "chre/platform/assert.h" #include "chre/platform/fatal_error.h" #include "chre/util/system/debug_dump.h" namespace chre { GnssRequestManager::GnssRequestManager() : mCurrentLocationSessionInterval(UINT64_MAX) { if (!mLocationSessionRequests.reserve(1)) { FATAL_ERROR("Failed to allocate GNSS requests list at startup"); } } void GnssRequestManager::init() { mPlatformGnss.init(); } uint32_t GnssRequestManager::getCapabilities() { return mPlatformGnss.getCapabilities(); } bool GnssRequestManager::startLocationSession(Nanoapp *nanoapp, Milliseconds minInterval, Milliseconds minTimeToNextFix, const void *cookie) { CHRE_ASSERT(nanoapp); return configureLocationSession(nanoapp, true /* enable */, minInterval, minTimeToNextFix, cookie); } bool GnssRequestManager::stopLocationSession(Nanoapp *nanoapp, const void *cookie) { CHRE_ASSERT(nanoapp); return configureLocationSession(nanoapp, false /* enable */, Milliseconds(UINT64_MAX), Milliseconds(UINT64_MAX), cookie); } void GnssRequestManager::handleLocationSessionStatusChange(bool enabled, uint8_t errorCode) { struct CallbackState { bool enabled; uint8_t errorCode; }; auto *cbState = memoryAlloc(); if (cbState == nullptr) { LOGE("Failed to allocate callback state for location session state change"); } else { cbState->enabled = enabled; cbState->errorCode = errorCode; auto callback = [](uint16_t /* eventType */, void *eventData) { auto *state = static_cast(eventData); EventLoopManagerSingleton::get()->getGnssRequestManager() .handleLocationSessionStatusChangeSync(state->enabled, state->errorCode); memoryFree(state); }; bool callbackDeferred = EventLoopManagerSingleton::get()->deferCallback( SystemCallbackType::GnssLocationSessionStatusChange, cbState, callback); if (!callbackDeferred) { memoryFree(cbState); } } } void GnssRequestManager::handleLocationEvent(chreGnssLocationEvent *event) { bool eventPosted = EventLoopManagerSingleton::get()->getEventLoop() .postEvent(CHRE_EVENT_GNSS_LOCATION, event, freeLocationEventCallback, kSystemInstanceId, kBroadcastInstanceId); if (!eventPosted) { FATAL_ERROR("Failed to send GNSS location event"); } } bool GnssRequestManager::logStateToBuffer(char *buffer, size_t *bufferPos, size_t bufferSize) const { bool success = debugDumpPrint(buffer, bufferPos, bufferSize, "\nGNSS:" " Current interval(ms)=%" PRIu64 "\n", mCurrentLocationSessionInterval. getMilliseconds()); success &= debugDumpPrint(buffer, bufferPos, bufferSize, " GNSS requests:\n"); for (const auto& request : mLocationSessionRequests) { success &= debugDumpPrint(buffer, bufferPos, bufferSize, " minInterval(ms)" "=%" PRIu64 " nanoappId=%" PRIu32 "\n", request.minInterval.getMilliseconds(), request.nanoappInstanceId); } success &= debugDumpPrint(buffer, bufferPos, bufferSize, " GNSS transition queue:\n"); for (const auto& transition : mLocationSessionStateTransitions) { success &= debugDumpPrint(buffer, bufferPos, bufferSize, " minInterval(ms)=%" PRIu64 " enable=%d" " nanoappId=%" PRIu32 "\n", transition.minInterval.getMilliseconds(), transition.enable, transition.nanoappInstanceId); } return success; } bool GnssRequestManager::configureLocationSession( Nanoapp *nanoapp, bool enable, Milliseconds minInterval, Milliseconds minTimeToFirstFix, const void *cookie) { bool success = false; uint32_t instanceId = nanoapp->getInstanceId(); size_t requestIndex = 0; bool nanoappHasRequest = nanoappHasLocationSessionRequest(instanceId, &requestIndex); if (!mLocationSessionStateTransitions.empty()) { success = addLocationSessionRequestToQueue(instanceId, enable, minInterval, cookie); } else if (locationSessionIsInRequestedState(enable, minInterval, nanoappHasRequest)) { success = postLocationSessionAsyncResultEvent( instanceId, true /* success */, enable, minInterval, CHRE_ERROR_NONE, cookie); } else if (locationSessionStateTransitionIsRequired(enable, minInterval, nanoappHasRequest, requestIndex)) { success = addLocationSessionRequestToQueue(instanceId, enable, minInterval, cookie); if (success) { // TODO: Provide support for min time to next fix. It is currently sent // to the platform as zero. success = mPlatformGnss.controlLocationSession(enable, minInterval, Milliseconds(0)); if (!success) { // TODO: Add a pop_back method. mLocationSessionStateTransitions.remove( mLocationSessionRequests.size() - 1); LOGE("Failed to enable a GNSS location session for nanoapp instance " "%" PRIu32, instanceId); } } } else { CHRE_ASSERT_LOG(false, "Invalid location session configuration"); } return success; } bool GnssRequestManager::nanoappHasLocationSessionRequest( uint32_t instanceId, size_t *requestIndex) { bool hasLocationSessionRequest = false; for (size_t i = 0; i < mLocationSessionRequests.size(); i++) { if (mLocationSessionRequests[i].nanoappInstanceId == instanceId) { hasLocationSessionRequest = true; if (requestIndex != nullptr) { *requestIndex = i; } break; } } return hasLocationSessionRequest; } bool GnssRequestManager::addLocationSessionRequestToQueue( uint32_t instanceId, bool enable, Milliseconds minInterval, const void *cookie) { LocationSessionStateTransition stateTransition; stateTransition.nanoappInstanceId = instanceId; stateTransition.enable = enable; stateTransition.minInterval = minInterval; stateTransition.cookie = cookie; bool success = mLocationSessionStateTransitions.push(stateTransition); if (!success) { LOGW("Too many location session state transitions"); } return success; } bool GnssRequestManager::locationSessionIsEnabled() { return !mLocationSessionRequests.empty(); } bool GnssRequestManager::locationSessionIsInRequestedState( bool requestedState, Milliseconds minInterval, bool nanoappHasRequest) { bool inTargetState = (requestedState == locationSessionIsEnabled()); bool meetsMinInterval = (minInterval >= mCurrentLocationSessionInterval); bool hasMoreThanOneRequest = (mLocationSessionRequests.size() > 1); return ((inTargetState && (!requestedState || meetsMinInterval)) || (!requestedState && (!nanoappHasRequest || hasMoreThanOneRequest))); } bool GnssRequestManager::locationSessionStateTransitionIsRequired( bool requestedState, Milliseconds minInterval, bool nanoappHasRequest, size_t requestIndex) { bool requestToEnable = (requestedState && !locationSessionIsEnabled()); bool requestToIncreaseRate = (requestedState && locationSessionIsEnabled() && minInterval < mCurrentLocationSessionInterval); bool requestToDisable = (!requestedState && nanoappHasRequest && mLocationSessionRequests.size() == 1); // An effective rate decrease for the location 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& currentRequest = mLocationSessionRequests[requestIndex]; for (size_t i = 0; i < mLocationSessionRequests.size(); i++) { LocationSessionRequest& request = mLocationSessionRequests[i]; if (i != requestIndex && request.minInterval == currentRequest.minInterval) { requestCount++; } } requestToDecreaseRate = (minInterval > mCurrentLocationSessionInterval && currentRequest.minInterval == mCurrentLocationSessionInterval && requestCount == 0); } return (requestToEnable || requestToDisable || requestToIncreaseRate || requestToDecreaseRate); } bool GnssRequestManager::updateLocationSessionRequests( bool enable, Milliseconds minInterval, uint32_t instanceId) { bool success = true; Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop() .findNanoappByInstanceId(instanceId); if (nanoapp == nullptr) { CHRE_ASSERT_LOG(false, "Failed to update location session request list for " "non-existent nanoapp"); } else { size_t requestIndex; bool hasExistingRequest = nanoappHasLocationSessionRequest(instanceId, &requestIndex); if (enable) { if (hasExistingRequest) { // If the nanoapp has an open request ensure that the minInterval is // kept up to date. mLocationSessionRequests[requestIndex].minInterval = minInterval; } else { success = nanoapp->registerForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); if (!success) { LOGE("Failed to register nanoapp for GNSS location events"); } else { // The location session was successfully enabled for this nanoapp and // there is no existing request. Add it to the list of location // session nanoapps. LocationSessionRequest locationSessionRequest; locationSessionRequest.nanoappInstanceId = instanceId; locationSessionRequest.minInterval = minInterval; success = mLocationSessionRequests.push_back(locationSessionRequest); if (!success) { nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); LOGE("Failed to add nanoapp to the list of location session " "nanoapps"); } } } } else { if (!hasExistingRequest) { success = false; LOGE("Received a location session state change for a non-existent " "nanoapp"); } else { // The location session was successfully disabled for a previously // enabled nanoapp. Remove it from the list of requests. mLocationSessionRequests.erase(requestIndex); nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); } } } return success; } bool GnssRequestManager::postLocationSessionAsyncResultEvent( uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, uint8_t errorCode, const void *cookie) { bool eventPosted = false; if (!success || updateLocationSessionRequests(enable, minInterval, instanceId)) { chreAsyncResult *event = memoryAlloc(); if (event == nullptr) { LOGE("Failed to allocate location session async result event"); } else { if (enable) { event->requestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START; } else { event->requestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP; } event->success = success; event->errorCode = errorCode; event->reserved = 0; event->cookie = cookie; eventPosted = EventLoopManagerSingleton::get()->getEventLoop() .postEvent(CHRE_EVENT_GNSS_ASYNC_RESULT, event, freeEventDataCallback, kSystemInstanceId, instanceId); if (!eventPosted) { memoryFree(event); } } } return eventPosted; } void GnssRequestManager::postLocationSessionAsyncResultEventFatal( uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, uint8_t errorCode, const void *cookie) { if (!postLocationSessionAsyncResultEvent(instanceId, success, enable, minInterval, errorCode, cookie)) { FATAL_ERROR("Failed to send GNSS location request async result event"); } } void GnssRequestManager::handleLocationSessionStatusChangeSync( bool enabled, uint8_t errorCode) { bool success = (errorCode == CHRE_ERROR_NONE); CHRE_ASSERT_LOG(!mLocationSessionStateTransitions.empty(), "handleLocationSessionStatusChangeSync called with no " "transitions"); if (!mLocationSessionStateTransitions.empty()) { const auto& stateTransition = mLocationSessionStateTransitions.front(); if (success) { mCurrentLocationSessionInterval = stateTransition.minInterval; } success &= (stateTransition.enable == enabled); postLocationSessionAsyncResultEventFatal(stateTransition.nanoappInstanceId, success, stateTransition.enable, stateTransition.minInterval, errorCode, stateTransition.cookie); mLocationSessionStateTransitions.pop(); } while (!mLocationSessionStateTransitions.empty()) { const auto& stateTransition = mLocationSessionStateTransitions.front(); size_t requestIndex; bool nanoappHasRequest = nanoappHasLocationSessionRequest( stateTransition.nanoappInstanceId, &requestIndex); if (locationSessionStateTransitionIsRequired(stateTransition.enable, stateTransition.minInterval, nanoappHasRequest, requestIndex)) { if (mPlatformGnss.controlLocationSession(stateTransition.enable, stateTransition.minInterval, Milliseconds(0))) { break; } else { LOGE("Failed to enable a GNSS location session for nanoapp instance " "%" PRIu32, stateTransition.nanoappInstanceId); postLocationSessionAsyncResultEventFatal( stateTransition.nanoappInstanceId, false /* success */, stateTransition.enable, stateTransition.minInterval, CHRE_ERROR, stateTransition.cookie); mLocationSessionStateTransitions.pop(); } } else { postLocationSessionAsyncResultEventFatal( stateTransition.nanoappInstanceId, true /* success */, stateTransition.enable, stateTransition.minInterval, errorCode, stateTransition.cookie); mLocationSessionStateTransitions.pop(); } } } void GnssRequestManager::handleFreeLocationEvent(chreGnssLocationEvent *event) { mPlatformGnss.releaseLocationEvent(event); } void GnssRequestManager::freeLocationEventCallback(uint16_t eventType, void *eventData) { auto *locationEvent = static_cast(eventData); EventLoopManagerSingleton::get()->getGnssRequestManager() .handleFreeLocationEvent(locationEvent); } } // namespace chre