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