gnss_request_manager.cc revision 7e76ba4dbeea9ea33132bf31b7c3887284c4bd87
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
24namespace chre {
25
26GnssRequestManager::GnssRequestManager()
27    : mCurrentLocationSessionInterval(UINT64_MAX) {
28  if (!mLocationSessionRequests.reserve(1)) {
29    FATAL_ERROR("Failed to allocate GNSS requests list at startup");
30  }
31}
32
33void GnssRequestManager::init() {
34  mPlatformGnss.init();
35}
36
37uint32_t GnssRequestManager::getCapabilities() {
38  return mPlatformGnss.getCapabilities();
39}
40
41bool 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
50bool 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
58void 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
88void 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
97bool 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
127bool 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
168bool 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
185bool 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
202bool GnssRequestManager::locationSessionIsEnabled() {
203  return !mLocationSessionRequests.empty();
204}
205
206bool 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
215bool 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
252bool 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
305bool 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
339void 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
348void 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
404void GnssRequestManager::handleFreeLocationEvent(chreGnssLocationEvent *event) {
405  mPlatformGnss.releaseLocationEvent(event);
406}
407
408void 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