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