gnss_manager.cc revision 10d594714109f837bd5bfe819977eb357b5dbf83
1/* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "chre/core/gnss_manager.h" 18 19#include "chre/core/event_loop_manager.h" 20#include "chre/platform/assert.h" 21#include "chre/platform/fatal_error.h" 22#include "chre/util/system/debug_dump.h" 23 24namespace chre { 25 26GnssManager::GnssManager() 27 : mLocationSession(CHRE_EVENT_GNSS_LOCATION) { 28} 29 30void GnssManager::init() { 31 mPlatformGnss.init(); 32} 33 34uint32_t GnssManager::getCapabilities() { 35 return mPlatformGnss.getCapabilities(); 36} 37 38bool GnssManager::logStateToBuffer( 39 char *buffer, size_t *bufferPos, size_t bufferSize) const { 40 bool success = debugDumpPrint(buffer, bufferPos, bufferSize,"\nGNSS:"); 41 success &= mLocationSession.logStateToBuffer(buffer, bufferPos, bufferSize); 42 return success; 43} 44 45GnssSession::GnssSession(uint16_t reportEventType) 46 : mReportEventType(reportEventType) { 47 switch (mReportEventType) { 48 case CHRE_EVENT_GNSS_LOCATION: 49 mStartRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START; 50 mStopRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP; 51 mName = "Location"; 52 break; 53 54 default: 55 CHRE_ASSERT_LOG(false, "Unsupported eventType %" PRIu16, reportEventType); 56 } 57 58 if (!mRequests.reserve(1)) { 59 FATAL_ERROR_OOM(); 60 } 61} 62 63bool GnssSession::addRequest(Nanoapp *nanoapp, Milliseconds minInterval, 64 Milliseconds minTimeToNext, const void *cookie) { 65 CHRE_ASSERT(nanoapp); 66 return configure(nanoapp, true /* enable */, minInterval, minTimeToNext, 67 cookie); 68} 69 70bool GnssSession::removeRequest(Nanoapp *nanoapp, const void *cookie) { 71 CHRE_ASSERT(nanoapp); 72 return configure(nanoapp, false /* enable */, Milliseconds(UINT64_MAX), 73 Milliseconds(UINT64_MAX), cookie); 74} 75 76void GnssSession::handleStatusChange(bool enabled, uint8_t errorCode) { 77 struct CallbackState { 78 bool enabled; 79 uint8_t errorCode; 80 GnssSession *session; 81 }; 82 83 auto *cbState = memoryAlloc<CallbackState>(); 84 if (cbState == nullptr) { 85 LOGE("Failed to allocate callback state for GNSS session state change"); 86 } else { 87 cbState->enabled = enabled; 88 cbState->errorCode = errorCode; 89 cbState->session = this; 90 91 auto callback = [](uint16_t /* eventType */, void *eventData) { 92 auto *state = static_cast<CallbackState *>(eventData); 93 state->session->handleStatusChangeSync(state->enabled, state->errorCode); 94 memoryFree(state); 95 }; 96 97 EventLoopManagerSingleton::get()->deferCallback( 98 SystemCallbackType::GnssSessionStatusChange, cbState, callback); 99 } 100} 101 102void GnssSession::handleReportEvent(void *event) { 103 EventLoopManagerSingleton::get()->getEventLoop() 104 .postEvent(mReportEventType, event, freeReportEventCallback); 105} 106 107bool GnssSession::logStateToBuffer( 108 char *buffer, size_t *bufferPos, size_t bufferSize) const { 109 bool success = debugDumpPrint(buffer, bufferPos, bufferSize, 110 "\n %s: Current interval(ms)=%" PRIu64 "\n", 111 mName, mCurrentInterval.getMilliseconds()); 112 113 success &= debugDumpPrint(buffer, bufferPos, bufferSize, " Requests:\n"); 114 for (const auto& request : mRequests) { 115 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 116 " minInterval(ms)=%" PRIu64 " nanoappId=%" 117 PRIu32 "\n", 118 request.minInterval.getMilliseconds(), 119 request.nanoappInstanceId); 120 } 121 122 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 123 " Transition queue:\n"); 124 for (const auto& transition : mStateTransitions) { 125 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 126 " minInterval(ms)=%" PRIu64 " enable=%d" 127 " nanoappId=%" PRIu32 "\n", 128 transition.minInterval.getMilliseconds(), 129 transition.enable, transition.nanoappInstanceId); 130 } 131 132 return success; 133} 134 135bool GnssSession::configure( 136 Nanoapp *nanoapp, bool enable, Milliseconds minInterval, 137 Milliseconds minTimeToNext, const void *cookie) { 138 bool success = false; 139 uint32_t instanceId = nanoapp->getInstanceId(); 140 size_t requestIndex = 0; 141 bool hasRequest = nanoappHasRequest(instanceId, &requestIndex); 142 if (!mStateTransitions.empty()) { 143 success = addRequestToQueue(instanceId, enable, minInterval, cookie); 144 } else if (isInRequestedState(enable, minInterval, hasRequest)) { 145 success = postAsyncResultEvent( 146 instanceId, true /* success */, enable, minInterval, CHRE_ERROR_NONE, 147 cookie); 148 } else if (stateTransitionIsRequired(enable, minInterval, hasRequest, 149 requestIndex)) { 150 success = addRequestToQueue(instanceId, enable, minInterval, cookie); 151 if (success) { 152 success = controlPlatform(enable, minInterval, minTimeToNext); 153 if (!success) { 154 mStateTransitions.pop_back(); 155 LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu32, 156 instanceId); 157 } 158 } 159 } else { 160 CHRE_ASSERT_LOG(false, "Invalid GNSS session configuration"); 161 } 162 163 return success; 164} 165 166bool GnssSession::nanoappHasRequest( 167 uint32_t instanceId, size_t *requestIndex) const { 168 bool hasRequest = false; 169 for (size_t i = 0; i < mRequests.size(); i++) { 170 if (mRequests[i].nanoappInstanceId == instanceId) { 171 hasRequest = true; 172 if (requestIndex != nullptr) { 173 *requestIndex = i; 174 } 175 176 break; 177 } 178 } 179 180 return hasRequest; 181} 182 183bool GnssSession::addRequestToQueue( 184 uint32_t instanceId, bool enable, Milliseconds minInterval, 185 const void *cookie) { 186 StateTransition stateTransition; 187 stateTransition.nanoappInstanceId = instanceId; 188 stateTransition.enable = enable; 189 stateTransition.minInterval = minInterval; 190 stateTransition.cookie = cookie; 191 192 bool success = mStateTransitions.push(stateTransition); 193 if (!success) { 194 LOGW("Too many session state transitions"); 195 } 196 197 return success; 198} 199 200bool GnssSession::isEnabled() const { 201 return !mRequests.empty(); 202} 203 204bool GnssSession::isInRequestedState( 205 bool requestedState, Milliseconds minInterval, bool nanoappHasRequest) 206 const { 207 bool inTargetState = (requestedState == isEnabled()); 208 bool meetsMinInterval = (minInterval >= mCurrentInterval); 209 bool hasMoreThanOneRequest = (mRequests.size() > 1); 210 return ((inTargetState && (!requestedState || meetsMinInterval)) 211 || (!requestedState && (!nanoappHasRequest || hasMoreThanOneRequest))); 212} 213 214bool GnssSession::stateTransitionIsRequired( 215 bool requestedState, Milliseconds minInterval, bool nanoappHasRequest, 216 size_t requestIndex) const { 217 bool requestToEnable = (requestedState && !isEnabled()); 218 bool requestToIncreaseRate = (requestedState && isEnabled() 219 && minInterval < mCurrentInterval); 220 bool requestToDisable = (!requestedState && nanoappHasRequest 221 && mRequests.size() == 1); 222 223 // An effective rate decrease for the session can only occur if the nanoapp 224 // has an existing request. 225 bool requestToDecreaseRate = false; 226 if (nanoappHasRequest) { 227 // The nanoapp has an existing request. Check that the request does not 228 // result in a rate decrease by checking if no other nanoapps have the 229 // same request, the nanoapp's existing request is not equal to the current 230 // requested interval and the new request is slower than the current 231 // requested rate. 232 size_t requestCount = 0; 233 const auto& currentRequest = mRequests[requestIndex]; 234 for (size_t i = 0; i < mRequests.size(); i++) { 235 const Request& request = mRequests[i]; 236 if (i != requestIndex 237 && request.minInterval == currentRequest.minInterval) { 238 requestCount++; 239 } 240 } 241 242 requestToDecreaseRate = (minInterval > mCurrentInterval 243 && currentRequest.minInterval == mCurrentInterval && requestCount == 0); 244 } 245 246 return (requestToEnable || requestToDisable || requestToIncreaseRate 247 || requestToDecreaseRate); 248} 249 250bool GnssSession::updateRequests( 251 bool enable, Milliseconds minInterval, uint32_t instanceId) { 252 bool success = true; 253 Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop() 254 .findNanoappByInstanceId(instanceId); 255 if (nanoapp == nullptr) { 256 LOGW("Failed to update GNSS session request list for non-existent nanoapp"); 257 } else { 258 size_t requestIndex; 259 bool hasExistingRequest = nanoappHasRequest(instanceId, &requestIndex); 260 if (enable) { 261 if (hasExistingRequest) { 262 // If the nanoapp has an open request ensure that the minInterval is 263 // kept up to date. 264 mRequests[requestIndex].minInterval = minInterval; 265 } else { 266 // The GNSS session was successfully enabled for this nanoapp and 267 // there is no existing request. Add it to the list of GNSS session 268 // nanoapps. 269 Request request; 270 request.nanoappInstanceId = instanceId; 271 request.minInterval = minInterval; 272 success = mRequests.push_back(request); 273 if (!success) { 274 LOGE("Failed to add nanoapp to the list of GNSS session nanoapps"); 275 } else { 276 nanoapp->registerForBroadcastEvent(mReportEventType); 277 } 278 } 279 } else { 280 if (!hasExistingRequest) { 281 success = false; 282 LOGE("Received a GNSS session state change for a non-existent nanoapp"); 283 } else { 284 // The session was successfully disabled for a previously enabled 285 // nanoapp. Remove it from the list of requests. 286 mRequests.erase(requestIndex); 287 nanoapp->unregisterForBroadcastEvent(mReportEventType); 288 } 289 } 290 } 291 292 return success; 293} 294 295bool GnssSession::postAsyncResultEvent( 296 uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, 297 uint8_t errorCode, const void *cookie) { 298 bool eventPosted = false; 299 if (!success || updateRequests(enable, minInterval, instanceId)) { 300 chreAsyncResult *event = memoryAlloc<chreAsyncResult>(); 301 if (event == nullptr) { 302 LOGE("Failed to allocate GNSS session async result event"); 303 } else { 304 event->requestType = enable ? mStartRequestType : mStopRequestType; 305 event->success = success; 306 event->errorCode = errorCode; 307 event->reserved = 0; 308 event->cookie = cookie; 309 310 eventPosted = EventLoopManagerSingleton::get()->getEventLoop() 311 .postEvent(CHRE_EVENT_GNSS_ASYNC_RESULT, event, freeEventDataCallback, 312 kSystemInstanceId, instanceId); 313 314 if (!eventPosted) { 315 memoryFree(event); 316 } 317 } 318 } 319 320 return eventPosted; 321} 322 323void GnssSession::postAsyncResultEventFatal( 324 uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, 325 uint8_t errorCode, const void *cookie) { 326 if (!postAsyncResultEvent(instanceId, success, enable, minInterval, errorCode, 327 cookie)) { 328 FATAL_ERROR("Failed to send GNSS session request async result event"); 329 } 330} 331 332void GnssSession::handleStatusChangeSync(bool enabled, uint8_t errorCode) { 333 bool success = (errorCode == CHRE_ERROR_NONE); 334 335 CHRE_ASSERT_LOG(!mStateTransitions.empty(), 336 "handleStatusChangeSync called with no transitions"); 337 if (!mStateTransitions.empty()) { 338 const auto& stateTransition = mStateTransitions.front(); 339 340 if (success) { 341 mCurrentInterval = stateTransition.minInterval; 342 } 343 344 success &= (stateTransition.enable == enabled); 345 postAsyncResultEventFatal(stateTransition.nanoappInstanceId, success, 346 stateTransition.enable, 347 stateTransition.minInterval, 348 errorCode, stateTransition.cookie); 349 mStateTransitions.pop(); 350 } 351 352 while (!mStateTransitions.empty()) { 353 const auto& stateTransition = mStateTransitions.front(); 354 355 size_t requestIndex; 356 bool hasRequest = nanoappHasRequest( 357 stateTransition.nanoappInstanceId, &requestIndex); 358 359 if (stateTransitionIsRequired(stateTransition.enable, 360 stateTransition.minInterval, 361 hasRequest, requestIndex)) { 362 if (controlPlatform(stateTransition.enable, stateTransition.minInterval, 363 Milliseconds(0))) { 364 break; 365 } else { 366 LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu32, 367 stateTransition.nanoappInstanceId); 368 postAsyncResultEventFatal( 369 stateTransition.nanoappInstanceId, false /* success */, 370 stateTransition.enable, stateTransition.minInterval, 371 CHRE_ERROR, stateTransition.cookie); 372 mStateTransitions.pop(); 373 } 374 } else { 375 postAsyncResultEventFatal( 376 stateTransition.nanoappInstanceId, true /* success */, 377 stateTransition.enable, stateTransition.minInterval, 378 errorCode, stateTransition.cookie); 379 mStateTransitions.pop(); 380 } 381 } 382} 383 384void GnssSession::freeReportEventCallback(uint16_t eventType, void *eventData) { 385 switch (eventType) { 386 case CHRE_EVENT_GNSS_LOCATION: 387 EventLoopManagerSingleton::get()->getGnssManager().mPlatformGnss 388 .releaseLocationEvent( 389 static_cast<chreGnssLocationEvent *>(eventData)); 390 break; 391 392 default: 393 CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, eventType); 394 } 395} 396 397bool GnssSession::controlPlatform( 398 bool enable, Milliseconds minInterval, Milliseconds /* minTimeToNext */) { 399 bool success = false; 400 401 switch (mReportEventType) { 402 case CHRE_EVENT_GNSS_LOCATION: 403 // TODO: Provide support for min time to next report. It is currently sent 404 // to the platform as zero. 405 success = EventLoopManagerSingleton::get()->getGnssManager().mPlatformGnss 406 .controlLocationSession(enable, minInterval, Milliseconds(0)); 407 break; 408 409 default: 410 CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, mReportEventType); 411 } 412 return success; 413} 414 415} // namespace chre 416