| /* |
| * 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 "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 { |
| |
| 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::logStateToBuffer( |
| char *buffer, size_t *bufferPos, size_t bufferSize) const { |
| debugDumpPrint(buffer, bufferPos, bufferSize,"\nGNSS:"); |
| mLocationSession.logStateToBuffer(buffer, bufferPos, bufferSize); |
| mMeasurementSession.logStateToBuffer(buffer, bufferPos, bufferSize); |
| } |
| |
| GnssSession::GnssSession(uint16_t reportEventType) |
| : mReportEventType(reportEventType) { |
| switch (mReportEventType) { |
| 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; |
| GnssSession *session; |
| }; |
| |
| auto *cbState = memoryAlloc<CallbackState>(); |
| if (cbState == nullptr) { |
| LOG_OOM(); |
| } else { |
| cbState->enabled = enabled; |
| cbState->errorCode = errorCode; |
| cbState->session = this; |
| |
| auto callback = [](uint16_t /* eventType */, void *eventData) { |
| auto *state = static_cast<CallbackState *>(eventData); |
| state->session->handleStatusChangeSync(state->enabled, state->errorCode); |
| memoryFree(state); |
| }; |
| |
| EventLoopManagerSingleton::get()->deferCallback( |
| SystemCallbackType::GnssSessionStatusChange, cbState, callback); |
| } |
| } |
| |
| void GnssSession::handleReportEvent(void *event) { |
| EventLoopManagerSingleton::get()->getEventLoop() |
| .postEvent(mReportEventType, event, freeReportEventCallback); |
| } |
| |
| void GnssSession::logStateToBuffer( |
| char *buffer, size_t *bufferPos, size_t bufferSize) const { |
| debugDumpPrint(buffer, bufferPos, bufferSize, |
| "\n %s: Current interval(ms)=%" PRIu64 "\n", |
| mName, mCurrentInterval.getMilliseconds()); |
| debugDumpPrint(buffer, bufferPos, bufferSize, " Requests:\n"); |
| for (const auto& request : mRequests) { |
| debugDumpPrint(buffer, bufferPos, bufferSize, |
| " minInterval(ms)=%" PRIu64 " nanoappId=%" |
| PRIu32 "\n", |
| request.minInterval.getMilliseconds(), |
| request.nanoappInstanceId); |
| } |
| |
| debugDumpPrint(buffer, bufferPos, bufferSize, " Transition queue:\n"); |
| for (const auto& transition : mStateTransitions) { |
| debugDumpPrint(buffer, bufferPos, bufferSize, |
| " minInterval(ms)=%" PRIu64 " enable=%d" |
| " nanoappId=%" PRIu32 "\n", |
| transition.minInterval.getMilliseconds(), |
| transition.enable, transition.nanoappInstanceId); |
| } |
| } |
| |
| bool GnssSession::configure( |
| Nanoapp *nanoapp, bool enable, Milliseconds minInterval, |
| Milliseconds minTimeToNext, const void *cookie) { |
| bool success = false; |
| uint32_t instanceId = nanoapp->getInstanceId(); |
| size_t requestIndex = 0; |
| bool hasRequest = nanoappHasRequest(instanceId, &requestIndex); |
| if (!mStateTransitions.empty()) { |
| success = addRequestToQueue(instanceId, enable, minInterval, cookie); |
| } else if (stateTransitionIsRequired(enable, minInterval, hasRequest, |
| requestIndex)) { |
| success = addRequestToQueue(instanceId, enable, minInterval, cookie); |
| if (success) { |
| success = controlPlatform(enable, minInterval, minTimeToNext); |
| if (!success) { |
| mStateTransitions.pop_back(); |
| LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu32, |
| instanceId); |
| } |
| } |
| } else { |
| success = postAsyncResultEvent( |
| instanceId, true /* success */, enable, minInterval, CHRE_ERROR_NONE, |
| cookie); |
| } |
| |
| return success; |
| } |
| |
| bool GnssSession::nanoappHasRequest( |
| uint32_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::addRequestToQueue( |
| uint32_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& currentRequest = 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, uint32_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(mReportEventType); |
| } |
| } |
| } else if (hasExistingRequest) { |
| // The session was successfully disabled for a previously enabled |
| // nanoapp. Remove it from the list of requests. |
| mRequests.erase(requestIndex); |
| nanoapp->unregisterForBroadcastEvent(mReportEventType); |
| } // else disabling an inactive request, treat as success per CHRE API |
| } |
| |
| return success; |
| } |
| |
| bool GnssSession::postAsyncResultEvent( |
| uint32_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<chreAsyncResult>(); |
| if (event == nullptr) { |
| LOG_OOM(); |
| } else { |
| event->requestType = enable ? mStartRequestType : mStopRequestType; |
| 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 GnssSession::postAsyncResultEventFatal( |
| uint32_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); |
| |
| CHRE_ASSERT_LOG(!mStateTransitions.empty(), |
| "handleStatusChangeSync called with no transitions"); |
| if (!mStateTransitions.empty()) { |
| const auto& stateTransition = mStateTransitions.front(); |
| |
| if (success) { |
| mCurrentInterval = stateTransition.minInterval; |
| } |
| |
| success &= (stateTransition.enable == enabled); |
| postAsyncResultEventFatal(stateTransition.nanoappInstanceId, success, |
| stateTransition.enable, |
| stateTransition.minInterval, |
| errorCode, stateTransition.cookie); |
| mStateTransitions.pop(); |
| } |
| |
| while (!mStateTransitions.empty()) { |
| const auto& stateTransition = mStateTransitions.front(); |
| |
| size_t requestIndex; |
| bool hasRequest = nanoappHasRequest( |
| stateTransition.nanoappInstanceId, &requestIndex); |
| |
| if (stateTransitionIsRequired(stateTransition.enable, |
| stateTransition.minInterval, |
| hasRequest, requestIndex)) { |
| if (controlPlatform(stateTransition.enable, stateTransition.minInterval, |
| Milliseconds(0))) { |
| break; |
| } else { |
| LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu32, |
| 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(); |
| } |
| } |
| } |
| |
| void GnssSession::freeReportEventCallback(uint16_t eventType, void *eventData) { |
| switch (eventType) { |
| case CHRE_EVENT_GNSS_LOCATION: |
| EventLoopManagerSingleton::get()->getGnssManager().mPlatformGnss |
| .releaseLocationEvent( |
| static_cast<chreGnssLocationEvent *>(eventData)); |
| break; |
| |
| case CHRE_EVENT_GNSS_DATA: |
| EventLoopManagerSingleton::get()->getGnssManager().mPlatformGnss |
| .releaseMeasurementDataEvent( |
| static_cast<chreGnssDataEvent *>(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 (mReportEventType) { |
| 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, mReportEventType); |
| } |
| return success; |
| } |
| |
| } // namespace chre |