/*
* 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