You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
16 KiB
415 lines
16 KiB
/* |
|
* 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<CallbackState>(); |
|
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<CallbackState *>(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<chreAsyncResult>(); |
|
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<chreGnssLocationEvent *>(eventData); |
|
EventLoopManagerSingleton::get()->getGnssRequestManager() |
|
.handleFreeLocationEvent(locationEvent); |
|
} |
|
|
|
} // namespace chre
|
|
|