blob: b24c613d9ecf613dda8f53fca49381bc83984911 [file] [log] [blame]
/*
* 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