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