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/core/settings.h"
21 #include "chre/platform/assert.h"
22 #include "chre/platform/fatal_error.h"
23 #include "chre/util/system/debug_dump.h"
24 
25 namespace chre {
26 
27 namespace {
28 
29 bool getCallbackType(uint16_t eventType, SystemCallbackType *callbackType) {
30   bool success = true;
31   switch (eventType) {
32     case CHRE_EVENT_GNSS_LOCATION: {
33       *callbackType = SystemCallbackType::GnssLocationReportEvent;
34       break;
35     }
36     case CHRE_EVENT_GNSS_DATA: {
37       *callbackType = SystemCallbackType::GnssMeasurementReportEvent;
38       break;
39     }
40     default: {
41       LOGE("Unknown event type %" PRIu16, eventType);
42       success = false;
43     }
44   }
45 
46   return success;
47 }
48 
49 bool getReportEventType(SystemCallbackType callbackType, uint16_t *eventType) {
50   bool success = true;
51   switch (callbackType) {
52     case SystemCallbackType::GnssLocationReportEvent: {
53       *eventType = CHRE_EVENT_GNSS_LOCATION;
54       break;
55     }
56     case SystemCallbackType::GnssMeasurementReportEvent: {
57       *eventType = CHRE_EVENT_GNSS_DATA;
58       break;
59     }
60     default: {
61       LOGE("Unknown callback type %" PRIu16,
62            static_cast<uint16_t>(callbackType));
63       success = false;
64     }
65   }
66 
67   return success;
68 }
69 
70 }  // anonymous namespace
71 
72 GnssManager::GnssManager()
73     : mLocationSession(CHRE_EVENT_GNSS_LOCATION),
74       mMeasurementSession(CHRE_EVENT_GNSS_DATA) {}
75 
76 void GnssManager::init() {
77   mPlatformGnss.init();
78 }
79 
80 uint32_t GnssManager::getCapabilities() {
81   return mPlatformGnss.getCapabilities();
82 }
83 
84 void GnssManager::onSettingChanged(Setting setting, SettingState state) {
85   mLocationSession.onSettingChanged(setting, state);
86   mMeasurementSession.onSettingChanged(setting, state);
87 }
88 
89 void GnssManager::logStateToBuffer(DebugDumpWrapper &debugDump) const {
90   debugDump.print("\nGNSS:");
91   mLocationSession.logStateToBuffer(debugDump);
92   mMeasurementSession.logStateToBuffer(debugDump);
93 }
94 
95 GnssSession::GnssSession(uint16_t reportEventType)
96     : mReportEventType(reportEventType) {
97   switch (mReportEventType) {
98     case CHRE_EVENT_GNSS_LOCATION:
99       mStartRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START;
100       mStopRequestType = CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP;
101       mName = "Location";
102       break;
103 
104     case CHRE_EVENT_GNSS_DATA:
105       mStartRequestType = CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_START;
106       mStopRequestType = CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_STOP;
107       mName = "Measurement";
108       break;
109 
110     default:
111       CHRE_ASSERT_LOG(false, "Unsupported eventType %" PRIu16, reportEventType);
112   }
113 
114   if (!mRequests.reserve(1)) {
115     FATAL_ERROR_OOM();
116   }
117 }
118 
119 bool GnssSession::addRequest(Nanoapp *nanoapp, Milliseconds minInterval,
120                              Milliseconds minTimeToNext, const void *cookie) {
121   CHRE_ASSERT(nanoapp);
122   return configure(nanoapp, true /* enable */, minInterval, minTimeToNext,
123                    cookie);
124 }
125 
126 bool GnssSession::removeRequest(Nanoapp *nanoapp, const void *cookie) {
127   CHRE_ASSERT(nanoapp);
128   return configure(nanoapp, false /* enable */, Milliseconds(UINT64_MAX),
129                    Milliseconds(UINT64_MAX), cookie);
130 }
131 
132 void GnssSession::handleStatusChange(bool enabled, uint8_t errorCode) {
133   struct CallbackState {
134     bool enabled;
135     uint8_t errorCode;
136     GnssSession *session;
137   };
138 
139   auto *cbState = memoryAlloc<CallbackState>();
140   if (cbState == nullptr) {
141     LOG_OOM();
142   } else {
143     cbState->enabled = enabled;
144     cbState->errorCode = errorCode;
145     cbState->session = this;
146 
147     auto callback = [](uint16_t /* eventType */, void *eventData) {
148       auto *state = static_cast<CallbackState *>(eventData);
149       state->session->handleStatusChangeSync(state->enabled, state->errorCode);
150       memoryFree(state);
151     };
152 
153     EventLoopManagerSingleton::get()->deferCallback(
154         SystemCallbackType::GnssSessionStatusChange, cbState, callback);
155   }
156 }
157 
158 void GnssSession::handleReportEvent(void *event) {
159   auto callback = [](uint16_t type, void *eventData) {
160     uint16_t reportEventType;
161     if (!getReportEventType(static_cast<SystemCallbackType>(type),
162                             &reportEventType) ||
163         (getSettingState(Setting::LOCATION) == SettingState::DISABLED)) {
164       freeReportEventCallback(reportEventType, eventData);
165     } else {
166       EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie(
167           reportEventType, eventData, freeReportEventCallback);
168     }
169   };
170 
171   SystemCallbackType type;
172   if (!getCallbackType(mReportEventType, &type)) {
173     freeReportEventCallback(mReportEventType, event);
174   } else {
175     EventLoopManagerSingleton::get()->deferCallback(type, event, callback);
176   }
177 }
178 
179 void GnssSession::onSettingChanged(Setting setting, SettingState state) {
180   if (setting == Setting::LOCATION) {
181     if (!mStateTransitions.empty()) {
182       // A request is in progress, so we wait until the async response arrives
183       // to handle the state change.
184       mSettingChangePending = true;
185     } else {
186       handleLocationSettingChange(state);
187       mSettingChangePending = false;
188     }
189   }
190 }
191 
192 void GnssSession::handleLocationSettingChange(SettingState state) {
193   bool chreDisable = ((state == SettingState::DISABLED) && mPlatformEnabled);
194   bool chreEnable = ((state == SettingState::ENABLED) && !mPlatformEnabled &&
195                      !mRequests.empty());
196 
197   if (chreEnable || chreDisable) {
198     if (controlPlatform(chreEnable, mCurrentInterval,
199                         Milliseconds(0) /* minTimeToNext */)) {
200       LOGD("Configured GNSS %s: setting state %" PRIu8, mName,
201            static_cast<uint8_t>(state));
202       addSessionRequestLog(CHRE_INSTANCE_ID, mCurrentInterval, chreEnable);
203       mInternalRequestPending = true;
204     } else {
205       LOGE("Failed to configure GNSS %s: setting state %" PRIu8, mName,
206            static_cast<uint8_t>(state));
207     }
208   }
209 }
210 
211 void GnssSession::logStateToBuffer(DebugDumpWrapper &debugDump) const {
212   // TODO: have all interval values print as INVALID if they are the max
213   // unsigned value
214   debugDump.print("\n %s: Curr int(ms)=%" PRIu64 "\n", mName,
215                   mCurrentInterval.getMilliseconds());
216   debugDump.print("  Requests:\n");
217   for (const auto &request : mRequests) {
218     debugDump.print("   minInt(ms)=%" PRIu64 " nappId=%" PRIu32 "\n",
219                     request.minInterval.getMilliseconds(),
220                     request.nanoappInstanceId);
221   }
222 
223   if (!mStateTransitions.empty()) {
224     debugDump.print("  Transition queue:\n");
225     for (const auto &transition : mStateTransitions) {
226       debugDump.print("   minInt(ms)=%" PRIu64 " enable=%d nappId=%" PRIu32
227                       "\n",
228                       transition.minInterval.getMilliseconds(),
229                       transition.enable, transition.nanoappInstanceId);
230     }
231   }
232 
233   debugDump.print("  Last %zu session requests:\n", mSessionRequestLogs.size());
234   static_assert(kNumSessionRequestLogs <= INT8_MAX,
235                 "kNumSessionRequestLogs must be less than INT8_MAX.");
236   for (int8_t i = static_cast<int8_t>(mSessionRequestLogs.size()) - 1; i >= 0;
237        i--) {
238     const auto &log = mSessionRequestLogs[static_cast<size_t>(i)];
239     debugDump.print("   ts=%" PRIu64 " nappId=%" PRIu32 " %s",
240                     log.timestamp.toRawNanoseconds(), log.instanceId,
241                     log.start ? "start" : "stop\n");
242     if (log.start) {
243       debugDump.print(" int(ms)=%" PRIu64 "\n", log.interval.getMilliseconds());
244     }
245   }
246 }
247 
248 bool GnssSession::configure(Nanoapp *nanoapp, bool enable,
249                             Milliseconds minInterval,
250                             Milliseconds minTimeToNext, const void *cookie) {
251   bool success = false;
252   uint32_t instanceId = nanoapp->getInstanceId();
253   size_t requestIndex = 0;
254   bool hasRequest = nanoappHasRequest(instanceId, &requestIndex);
255   if (!mStateTransitions.empty()) {
256     success = addRequestToQueue(instanceId, enable, minInterval, cookie);
257   } else if (stateTransitionIsRequired(enable, minInterval, hasRequest,
258                                        requestIndex)) {
259     if (enable &&
260         getSettingState(Setting::LOCATION) == SettingState::DISABLED) {
261       // Treat as success but post async failure per API.
262       success = postAsyncResultEvent(instanceId, false /* success */, enable,
263                                      minInterval, CHRE_ERROR_FUNCTION_DISABLED,
264                                      cookie);
265     } else if (addRequestToQueue(instanceId, enable, minInterval, cookie)) {
266       success = controlPlatform(enable, minInterval, minTimeToNext);
267       if (!success) {
268         mStateTransitions.pop_back();
269         LOGE("Failed to request a GNSS session for nanoapp instance %" PRIu32
270              " enable %d",
271              instanceId, enable);
272       }
273     }
274   } else {
275     success = postAsyncResultEvent(instanceId, true /* success */, enable,
276                                    minInterval, CHRE_ERROR_NONE, cookie);
277   }
278 
279   if (success) {
280     addSessionRequestLog(nanoapp->getInstanceId(), minInterval, enable);
281   }
282 
283   return success;
284 }
285 
286 bool GnssSession::nanoappHasRequest(uint32_t instanceId,
287                                     size_t *requestIndex) const {
288   bool hasRequest = false;
289   for (size_t i = 0; i < mRequests.size(); i++) {
290     if (mRequests[i].nanoappInstanceId == instanceId) {
291       hasRequest = true;
292       if (requestIndex != nullptr) {
293         *requestIndex = i;
294       }
295 
296       break;
297     }
298   }
299 
300   return hasRequest;
301 }
302 
303 bool GnssSession::addRequestToQueue(uint32_t instanceId, bool enable,
304                                     Milliseconds minInterval,
305                                     const void *cookie) {
306   StateTransition stateTransition;
307   stateTransition.nanoappInstanceId = instanceId;
308   stateTransition.enable = enable;
309   stateTransition.minInterval = minInterval;
310   stateTransition.cookie = cookie;
311 
312   bool success = mStateTransitions.push(stateTransition);
313   if (!success) {
314     LOGW("Too many session state transitions");
315   }
316 
317   return success;
318 }
319 
320 bool GnssSession::isEnabled() const {
321   return !mRequests.empty();
322 }
323 
324 bool GnssSession::stateTransitionIsRequired(bool requestedState,
325                                             Milliseconds minInterval,
326                                             bool nanoappHasRequest,
327                                             size_t requestIndex) const {
328   bool requestToEnable = (requestedState && !isEnabled());
329   bool requestToIncreaseRate =
330       (requestedState && isEnabled() && minInterval < mCurrentInterval);
331   bool requestToDisable =
332       (!requestedState && nanoappHasRequest && mRequests.size() == 1);
333 
334   // An effective rate decrease for the session can only occur if the nanoapp
335   // has an existing request.
336   bool requestToDecreaseRate = false;
337   if (nanoappHasRequest) {
338     // The nanoapp has an existing request. Check that the request does not
339     // result in a rate decrease by checking if no other nanoapps have the
340     // same request, the nanoapp's existing request is not equal to the current
341     // requested interval and the new request is slower than the current
342     // requested rate.
343     size_t requestCount = 0;
344     const auto &currentRequest = mRequests[requestIndex];
345     for (size_t i = 0; i < mRequests.size(); i++) {
346       const Request &request = mRequests[i];
347       if (i != requestIndex &&
348           request.minInterval == currentRequest.minInterval) {
349         requestCount++;
350       }
351     }
352 
353     requestToDecreaseRate =
354         (minInterval > mCurrentInterval &&
355          currentRequest.minInterval == mCurrentInterval && requestCount == 0);
356   }
357 
358   return (requestToEnable || requestToDisable || requestToIncreaseRate ||
359           requestToDecreaseRate);
360 }
361 
362 bool GnssSession::updateRequests(bool enable, Milliseconds minInterval,
363                                  uint32_t instanceId) {
364   bool success = true;
365   Nanoapp *nanoapp =
366       EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
367           instanceId);
368   if (nanoapp == nullptr) {
369     LOGW("Failed to update GNSS session request list for non-existent nanoapp");
370   } else {
371     size_t requestIndex;
372     bool hasExistingRequest = nanoappHasRequest(instanceId, &requestIndex);
373     if (enable) {
374       if (hasExistingRequest) {
375         // If the nanoapp has an open request ensure that the minInterval is
376         // kept up to date.
377         mRequests[requestIndex].minInterval = minInterval;
378       } else {
379         // The GNSS session was successfully enabled for this nanoapp and
380         // there is no existing request. Add it to the list of GNSS session
381         // nanoapps.
382         Request request;
383         request.nanoappInstanceId = instanceId;
384         request.minInterval = minInterval;
385         success = mRequests.push_back(request);
386         if (!success) {
387           LOG_OOM();
388         } else {
389           nanoapp->registerForBroadcastEvent(mReportEventType);
390         }
391       }
392     } else if (hasExistingRequest) {
393       // The session was successfully disabled for a previously enabled
394       // nanoapp. Remove it from the list of requests.
395       mRequests.erase(requestIndex);
396       nanoapp->unregisterForBroadcastEvent(mReportEventType);
397     }  // else disabling an inactive request, treat as success per CHRE API
398   }
399 
400   return success;
401 }
402 
403 bool GnssSession::postAsyncResultEvent(uint32_t instanceId, bool success,
404                                        bool enable, Milliseconds minInterval,
405                                        uint8_t errorCode, const void *cookie) {
406   bool eventPosted = false;
407   if (!success || updateRequests(enable, minInterval, instanceId)) {
408     chreAsyncResult *event = memoryAlloc<chreAsyncResult>();
409     if (event == nullptr) {
410       LOG_OOM();
411     } else {
412       event->requestType = enable ? mStartRequestType : mStopRequestType;
413       event->success = success;
414       event->errorCode = errorCode;
415       event->reserved = 0;
416       event->cookie = cookie;
417 
418       eventPosted =
419           EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie(
420               CHRE_EVENT_GNSS_ASYNC_RESULT, event, freeEventDataCallback,
421               instanceId);
422 
423       if (!eventPosted) {
424         memoryFree(event);
425       }
426     }
427   }
428 
429   return eventPosted;
430 }
431 
432 void GnssSession::postAsyncResultEventFatal(uint32_t instanceId, bool success,
433                                             bool enable,
434                                             Milliseconds minInterval,
435                                             uint8_t errorCode,
436                                             const void *cookie) {
437   if (!postAsyncResultEvent(instanceId, success, enable, minInterval, errorCode,
438                             cookie)) {
439     FATAL_ERROR("Failed to send GNSS session request async result event");
440   }
441 }
442 
443 void GnssSession::handleStatusChangeSync(bool enabled, uint8_t errorCode) {
444   bool success = (errorCode == CHRE_ERROR_NONE);
445 
446   CHRE_ASSERT_LOG(!mStateTransitions.empty() || mInternalRequestPending,
447                   "handleStatusChangeSync called with no transitions");
448   if (mInternalRequestPending) {
449     // Silently handle internal requests from CHRE, since they are not pushed
450     // to the mStateTransitions queue.
451     mInternalRequestPending = false;
452   } else if (!mStateTransitions.empty()) {
453     const auto &stateTransition = mStateTransitions.front();
454 
455     if (success) {
456       mCurrentInterval = stateTransition.minInterval;
457     }
458 
459     success &= (stateTransition.enable == enabled);
460     postAsyncResultEventFatal(
461         stateTransition.nanoappInstanceId, success, stateTransition.enable,
462         stateTransition.minInterval, errorCode, stateTransition.cookie);
463     mStateTransitions.pop();
464   }
465 
466   // If a previous setting change event is pending process, do that first.
467   if (mSettingChangePending) {
468     handleLocationSettingChange(getSettingState(Setting::LOCATION));
469     mSettingChangePending = false;
470   } else {
471     // Dispatch pending state transition until first one succeeds
472     dispatchQueuedStateTransitions();
473   }
474 }
475 
476 void GnssSession::freeReportEventCallback(uint16_t eventType, void *eventData) {
477   switch (eventType) {
478     case CHRE_EVENT_GNSS_LOCATION:
479       EventLoopManagerSingleton::get()
480           ->getGnssManager()
481           .mPlatformGnss.releaseLocationEvent(
482               static_cast<chreGnssLocationEvent *>(eventData));
483       break;
484 
485     case CHRE_EVENT_GNSS_DATA:
486       EventLoopManagerSingleton::get()
487           ->getGnssManager()
488           .mPlatformGnss.releaseMeasurementDataEvent(
489               static_cast<chreGnssDataEvent *>(eventData));
490       break;
491 
492     default:
493       CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, eventType);
494   }
495 }
496 
497 bool GnssSession::controlPlatform(bool enable, Milliseconds minInterval,
498                                   Milliseconds /* minTimeToNext */) {
499   bool success = false;
500 
501   switch (mReportEventType) {
502     case CHRE_EVENT_GNSS_LOCATION:
503       // TODO: Provide support for min time to next report. It is currently sent
504       // to the platform as zero.
505       success = EventLoopManagerSingleton::get()
506                     ->getGnssManager()
507                     .mPlatformGnss.controlLocationSession(enable, minInterval,
508                                                           Milliseconds(0));
509       break;
510 
511     case CHRE_EVENT_GNSS_DATA:
512       success =
513           EventLoopManagerSingleton::get()
514               ->getGnssManager()
515               .mPlatformGnss.controlMeasurementSession(enable, minInterval);
516       break;
517 
518     default:
519       CHRE_ASSERT_LOG(false, "Unhandled event type %" PRIu16, mReportEventType);
520   }
521 
522   if (success) {
523     mPlatformEnabled = enable;
524   }
525 
526   return success;
527 }
528 
529 void GnssSession::addSessionRequestLog(uint32_t nanoappInstanceId,
530                                        Milliseconds interval, bool start) {
531   mSessionRequestLogs.kick_push(SessionRequestLog(
532       SystemTime::getMonotonicTime(), nanoappInstanceId, interval, start));
533 }
534 
535 void GnssSession::dispatchQueuedStateTransitions() {
536   while (!mStateTransitions.empty()) {
537     const auto &stateTransition = mStateTransitions.front();
538 
539     size_t requestIndex;
540     bool hasRequest =
541         nanoappHasRequest(stateTransition.nanoappInstanceId, &requestIndex);
542 
543     if (stateTransitionIsRequired(stateTransition.enable,
544                                   stateTransition.minInterval, hasRequest,
545                                   requestIndex)) {
546       if (getSettingState(Setting::LOCATION) == SettingState::DISABLED) {
547         postAsyncResultEventFatal(
548             stateTransition.nanoappInstanceId, false /* success */,
549             stateTransition.enable, stateTransition.minInterval,
550             CHRE_ERROR_FUNCTION_DISABLED, stateTransition.cookie);
551         mStateTransitions.pop();
552       } else if (controlPlatform(stateTransition.enable,
553                                  stateTransition.minInterval,
554                                  Milliseconds(0))) {
555         break;
556       } else {
557         LOGE("Failed to enable a GNSS session for nanoapp instance %" PRIu32,
558              stateTransition.nanoappInstanceId);
559         postAsyncResultEventFatal(stateTransition.nanoappInstanceId,
560                                   false /* success */, stateTransition.enable,
561                                   stateTransition.minInterval, CHRE_ERROR,
562                                   stateTransition.cookie);
563         mStateTransitions.pop();
564       }
565     } else {
566       postAsyncResultEventFatal(stateTransition.nanoappInstanceId,
567                                 true /* success */, stateTransition.enable,
568                                 stateTransition.minInterval, CHRE_ERROR_NONE,
569                                 stateTransition.cookie);
570       mStateTransitions.pop();
571     }
572   }
573 }
574 
575 }  // namespace chre
576