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