1 /* 2 * Copyright (C) 2017 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_request_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 24 namespace chre { 25 26 GnssRequestManager::GnssRequestManager() 27 : mCurrentLocationSessionInterval(UINT64_MAX) { 28 if (!mLocationSessionRequests.reserve(1)) { 29 FATAL_ERROR("Failed to allocate GNSS requests list at startup"); 30 } 31 } 32 33 void GnssRequestManager::init() { 34 mPlatformGnss.init(); 35 } 36 37 uint32_t GnssRequestManager::getCapabilities() { 38 return mPlatformGnss.getCapabilities(); 39 } 40 41 bool GnssRequestManager::startLocationSession(Nanoapp *nanoapp, 42 Milliseconds minInterval, 43 Milliseconds minTimeToNextFix, 44 const void *cookie) { 45 CHRE_ASSERT(nanoapp); 46 return configureLocationSession(nanoapp, true /* enable */, minInterval, 47 minTimeToNextFix, cookie); 48 } 49 50 bool GnssRequestManager::stopLocationSession(Nanoapp *nanoapp, 51 const void *cookie) { 52 CHRE_ASSERT(nanoapp); 53 return configureLocationSession(nanoapp, false /* enable */, 54 Milliseconds(UINT64_MAX), 55 Milliseconds(UINT64_MAX), cookie); 56 } 57 58 void GnssRequestManager::handleLocationSessionStatusChange(bool enabled, 59 uint8_t errorCode) { 60 struct CallbackState { 61 bool enabled; 62 uint8_t errorCode; 63 }; 64 65 auto *cbState = memoryAlloc<CallbackState>(); 66 if (cbState == nullptr) { 67 LOGE("Failed to allocate callback state for location session state change"); 68 } else { 69 cbState->enabled = enabled; 70 cbState->errorCode = errorCode; 71 72 auto callback = [](uint16_t /* eventType */, void *eventData) { 73 auto *state = static_cast<CallbackState *>(eventData); 74 EventLoopManagerSingleton::get()->getGnssRequestManager() 75 .handleLocationSessionStatusChangeSync(state->enabled, 76 state->errorCode); 77 memoryFree(state); 78 }; 79 80 bool callbackDeferred = EventLoopManagerSingleton::get()->deferCallback( 81 SystemCallbackType::GnssLocationSessionStatusChange, cbState, callback); 82 if (!callbackDeferred) { 83 memoryFree(cbState); 84 } 85 } 86 } 87 88 void GnssRequestManager::handleLocationEvent(chreGnssLocationEvent *event) { 89 bool eventPosted = EventLoopManagerSingleton::get()->getEventLoop() 90 .postEvent(CHRE_EVENT_GNSS_LOCATION, event, freeLocationEventCallback, 91 kSystemInstanceId, kBroadcastInstanceId); 92 if (!eventPosted) { 93 FATAL_ERROR("Failed to send GNSS location event"); 94 } 95 } 96 97 bool GnssRequestManager::logStateToBuffer(char *buffer, size_t *bufferPos, 98 size_t bufferSize) const { 99 bool success = debugDumpPrint(buffer, bufferPos, bufferSize, "\nGNSS:" 100 " Current interval(ms)=%" PRIu64 "\n", 101 mCurrentLocationSessionInterval. 102 getMilliseconds()); 103 104 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 105 " GNSS requests:\n"); 106 for (const auto& request : mLocationSessionRequests) { 107 success &= debugDumpPrint(buffer, bufferPos, bufferSize, " minInterval(ms)" 108 "=%" PRIu64 " nanoappId=%" PRIu32 "\n", 109 request.minInterval.getMilliseconds(), 110 request.nanoappInstanceId); 111 } 112 113 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 114 " GNSS transition queue:\n"); 115 for (const auto& transition : mLocationSessionStateTransitions) { 116 success &= debugDumpPrint(buffer, bufferPos, bufferSize, 117 " minInterval(ms)=%" PRIu64 " enable=%d" 118 " nanoappId=%" PRIu32 "\n", 119 transition.minInterval.getMilliseconds(), 120 transition.enable, 121 transition.nanoappInstanceId); 122 } 123 124 return success; 125 } 126 127 bool GnssRequestManager::configureLocationSession( 128 Nanoapp *nanoapp, bool enable, Milliseconds minInterval, 129 Milliseconds minTimeToFirstFix, const void *cookie) { 130 bool success = false; 131 uint32_t instanceId = nanoapp->getInstanceId(); 132 size_t requestIndex = 0; 133 bool nanoappHasRequest = nanoappHasLocationSessionRequest(instanceId, 134 &requestIndex); 135 if (!mLocationSessionStateTransitions.empty()) { 136 success = addLocationSessionRequestToQueue(instanceId, enable, minInterval, 137 cookie); 138 } else if (locationSessionIsInRequestedState(enable, minInterval, 139 nanoappHasRequest)) { 140 success = postLocationSessionAsyncResultEvent( 141 instanceId, true /* success */, enable, minInterval, CHRE_ERROR_NONE, 142 cookie); 143 } else if (locationSessionStateTransitionIsRequired(enable, minInterval, 144 nanoappHasRequest, 145 requestIndex)) { 146 success = addLocationSessionRequestToQueue(instanceId, enable, 147 minInterval, cookie); 148 if (success) { 149 // TODO: Provide support for min time to next fix. It is currently sent 150 // to the platform as zero. 151 success = mPlatformGnss.controlLocationSession(enable, minInterval, 152 Milliseconds(0)); 153 if (!success) { 154 // TODO: Add a pop_back method. 155 mLocationSessionStateTransitions.remove( 156 mLocationSessionRequests.size() - 1); 157 LOGE("Failed to enable a GNSS location session for nanoapp instance " 158 "%" PRIu32, instanceId); 159 } 160 } 161 } else { 162 CHRE_ASSERT_LOG(false, "Invalid location session configuration"); 163 } 164 165 return success; 166 } 167 168 bool GnssRequestManager::nanoappHasLocationSessionRequest( 169 uint32_t instanceId, size_t *requestIndex) { 170 bool hasLocationSessionRequest = false; 171 for (size_t i = 0; i < mLocationSessionRequests.size(); i++) { 172 if (mLocationSessionRequests[i].nanoappInstanceId == instanceId) { 173 hasLocationSessionRequest = true; 174 if (requestIndex != nullptr) { 175 *requestIndex = i; 176 } 177 178 break; 179 } 180 } 181 182 return hasLocationSessionRequest; 183 } 184 185 bool GnssRequestManager::addLocationSessionRequestToQueue( 186 uint32_t instanceId, bool enable, Milliseconds minInterval, 187 const void *cookie) { 188 LocationSessionStateTransition stateTransition; 189 stateTransition.nanoappInstanceId = instanceId; 190 stateTransition.enable = enable; 191 stateTransition.minInterval = minInterval; 192 stateTransition.cookie = cookie; 193 194 bool success = mLocationSessionStateTransitions.push(stateTransition); 195 if (!success) { 196 LOGW("Too many location session state transitions"); 197 } 198 199 return success; 200 } 201 202 bool GnssRequestManager::locationSessionIsEnabled() { 203 return !mLocationSessionRequests.empty(); 204 } 205 206 bool GnssRequestManager::locationSessionIsInRequestedState( 207 bool requestedState, Milliseconds minInterval, bool nanoappHasRequest) { 208 bool inTargetState = (requestedState == locationSessionIsEnabled()); 209 bool meetsMinInterval = (minInterval >= mCurrentLocationSessionInterval); 210 bool hasMoreThanOneRequest = (mLocationSessionRequests.size() > 1); 211 return ((inTargetState && (!requestedState || meetsMinInterval)) 212 || (!requestedState && (!nanoappHasRequest || hasMoreThanOneRequest))); 213 } 214 215 bool GnssRequestManager::locationSessionStateTransitionIsRequired( 216 bool requestedState, Milliseconds minInterval, bool nanoappHasRequest, 217 size_t requestIndex) { 218 bool requestToEnable = (requestedState && !locationSessionIsEnabled()); 219 bool requestToIncreaseRate = (requestedState && locationSessionIsEnabled() 220 && minInterval < mCurrentLocationSessionInterval); 221 bool requestToDisable = (!requestedState && nanoappHasRequest 222 && mLocationSessionRequests.size() == 1); 223 224 // An effective rate decrease for the location session can only occur if the 225 // nanoapp has an existing request. 226 bool requestToDecreaseRate = false; 227 if (nanoappHasRequest) { 228 // The nanoapp has an existing request. Check that the request does not 229 // result in a rate decrease by checking if no other nanoapps have the 230 // same request, the nanoapp's existing request is not equal to the current 231 // requested interval and the new request is slower than the current 232 // requested rate. 233 size_t requestCount = 0; 234 const auto& currentRequest = mLocationSessionRequests[requestIndex]; 235 for (size_t i = 0; i < mLocationSessionRequests.size(); i++) { 236 LocationSessionRequest& request = mLocationSessionRequests[i]; 237 if (i != requestIndex 238 && request.minInterval == currentRequest.minInterval) { 239 requestCount++; 240 } 241 } 242 243 requestToDecreaseRate = (minInterval > mCurrentLocationSessionInterval 244 && currentRequest.minInterval == mCurrentLocationSessionInterval 245 && requestCount == 0); 246 } 247 248 return (requestToEnable || requestToDisable 249 || requestToIncreaseRate || requestToDecreaseRate); 250 } 251 252 bool GnssRequestManager::updateLocationSessionRequests( 253 bool enable, Milliseconds minInterval, uint32_t instanceId) { 254 bool success = true; 255 Nanoapp *nanoapp = EventLoopManagerSingleton::get()->getEventLoop() 256 .findNanoappByInstanceId(instanceId); 257 if (nanoapp == nullptr) { 258 CHRE_ASSERT_LOG(false, "Failed to update location session request list for " 259 "non-existent nanoapp"); 260 } else { 261 size_t requestIndex; 262 bool hasExistingRequest = nanoappHasLocationSessionRequest(instanceId, 263 &requestIndex); 264 if (enable) { 265 if (hasExistingRequest) { 266 // If the nanoapp has an open request ensure that the minInterval is 267 // kept up to date. 268 mLocationSessionRequests[requestIndex].minInterval = minInterval; 269 } else { 270 success = nanoapp->registerForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); 271 if (!success) { 272 LOGE("Failed to register nanoapp for GNSS location events"); 273 } else { 274 // The location session was successfully enabled for this nanoapp and 275 // there is no existing request. Add it to the list of location 276 // session nanoapps. 277 LocationSessionRequest locationSessionRequest; 278 locationSessionRequest.nanoappInstanceId = instanceId; 279 locationSessionRequest.minInterval = minInterval; 280 success = mLocationSessionRequests.push_back(locationSessionRequest); 281 if (!success) { 282 nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); 283 LOGE("Failed to add nanoapp to the list of location session " 284 "nanoapps"); 285 } 286 } 287 } 288 } else { 289 if (!hasExistingRequest) { 290 success = false; 291 LOGE("Received a location session state change for a non-existent " 292 "nanoapp"); 293 } else { 294 // The location session was successfully disabled for a previously 295 // enabled nanoapp. Remove it from the list of requests. 296 mLocationSessionRequests.erase(requestIndex); 297 nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_GNSS_LOCATION); 298 } 299 } 300 } 301 302 return success; 303 } 304 305 bool GnssRequestManager::postLocationSessionAsyncResultEvent( 306 uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, 307 uint8_t errorCode, const void *cookie) { 308 bool eventPosted = false; 309 if (!success || updateLocationSessionRequests(enable, minInterval, 310 instanceId)) { 311 chreAsyncResult *event = memoryAlloc<chreAsyncResult>(); 312 if (event == nullptr) { 313 LOGE("Failed to allocate location session async result event"); 314 } else { 315 if (enable) { 316 event->requestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START; 317 } else { 318 event->requestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP; 319 } 320 321 event->success = success; 322 event->errorCode = errorCode; 323 event->reserved = 0; 324 event->cookie = cookie; 325 326 eventPosted = EventLoopManagerSingleton::get()->getEventLoop() 327 .postEvent(CHRE_EVENT_GNSS_ASYNC_RESULT, event, freeEventDataCallback, 328 kSystemInstanceId, instanceId); 329 330 if (!eventPosted) { 331 memoryFree(event); 332 } 333 } 334 } 335 336 return eventPosted; 337 } 338 339 void GnssRequestManager::postLocationSessionAsyncResultEventFatal( 340 uint32_t instanceId, bool success, bool enable, Milliseconds minInterval, 341 uint8_t errorCode, const void *cookie) { 342 if (!postLocationSessionAsyncResultEvent(instanceId, success, enable, 343 minInterval, errorCode, cookie)) { 344 FATAL_ERROR("Failed to send GNSS location request async result event"); 345 } 346 } 347 348 void GnssRequestManager::handleLocationSessionStatusChangeSync( 349 bool enabled, uint8_t errorCode) { 350 bool success = (errorCode == CHRE_ERROR_NONE); 351 352 CHRE_ASSERT_LOG(!mLocationSessionStateTransitions.empty(), 353 "handleLocationSessionStatusChangeSync called with no " 354 "transitions"); 355 if (!mLocationSessionStateTransitions.empty()) { 356 const auto& stateTransition = mLocationSessionStateTransitions.front(); 357 358 if (success) { 359 mCurrentLocationSessionInterval = stateTransition.minInterval; 360 } 361 362 success &= (stateTransition.enable == enabled); 363 postLocationSessionAsyncResultEventFatal(stateTransition.nanoappInstanceId, 364 success, stateTransition.enable, 365 stateTransition.minInterval, 366 errorCode, stateTransition.cookie); 367 mLocationSessionStateTransitions.pop(); 368 } 369 370 while (!mLocationSessionStateTransitions.empty()) { 371 const auto& stateTransition = mLocationSessionStateTransitions.front(); 372 373 size_t requestIndex; 374 bool nanoappHasRequest = nanoappHasLocationSessionRequest( 375 stateTransition.nanoappInstanceId, &requestIndex); 376 377 if (locationSessionStateTransitionIsRequired(stateTransition.enable, 378 stateTransition.minInterval, 379 nanoappHasRequest, 380 requestIndex)) { 381 if (mPlatformGnss.controlLocationSession(stateTransition.enable, 382 stateTransition.minInterval, 383 Milliseconds(0))) { 384 break; 385 } else { 386 LOGE("Failed to enable a GNSS location session for nanoapp instance " 387 "%" PRIu32, stateTransition.nanoappInstanceId); 388 postLocationSessionAsyncResultEventFatal( 389 stateTransition.nanoappInstanceId, false /* success */, 390 stateTransition.enable, stateTransition.minInterval, 391 CHRE_ERROR, stateTransition.cookie); 392 mLocationSessionStateTransitions.pop(); 393 } 394 } else { 395 postLocationSessionAsyncResultEventFatal( 396 stateTransition.nanoappInstanceId, true /* success */, 397 stateTransition.enable, stateTransition.minInterval, 398 errorCode, stateTransition.cookie); 399 mLocationSessionStateTransitions.pop(); 400 } 401 } 402 } 403 404 void GnssRequestManager::handleFreeLocationEvent(chreGnssLocationEvent *event) { 405 mPlatformGnss.releaseLocationEvent(event); 406 } 407 408 void GnssRequestManager::freeLocationEventCallback(uint16_t eventType, 409 void *eventData) { 410 auto *locationEvent = static_cast<chreGnssLocationEvent *>(eventData); 411 EventLoopManagerSingleton::get()->getGnssRequestManager() 412 .handleFreeLocationEvent(locationEvent); 413 } 414 415 } // namespace chre 416