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