/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "guest/hals/gps/gps_thread.h"
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <cutils/log.h>
#include <cutils/sockets.h>
#include <hardware/gps.h>
// Calls an callback function to pass received and parsed GPS data to Android.
static void reader_call_callback(GpsDataReader* r) {
if (!r) {
ALOGW("%s: called with r=NULL", __FUNCTION__);
return;
}
if (!r->callback) {
ALOGW("%s: no callback registered; keeping the data to send later",
__FUNCTION__);
return;
}
if (!r->fix.flags) {
ALOGW("%s: no GPS fix", __FUNCTION__);
return;
}
// Always uses current time converted to UTC time in milliseconds.
time_t secs = time(NULL); // seconds from 01/01/1970.
r->fix.timestamp = (long long)secs * 1000;
#if GPS_DEBUG
D("* Parsed GPS Data");
if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) {
D(" - latitude = %g", r->fix.latitude);
D(" - longitude = %g", r->fix.longitude);
}
if (r->fix.flags & GPS_LOCATION_HAS_ALTITUDE)
D(" - altitude = %g", r->fix.altitude);
if (r->fix.flags & GPS_LOCATION_HAS_SPEED) D(" - speed = %g", r->fix.speed);
if (r->fix.flags & GPS_LOCATION_HAS_BEARING)
D(" - bearing = %g", r->fix.bearing);
if (r->fix.flags & GPS_LOCATION_HAS_ACCURACY)
D(" - accuracy = %g", r->fix.accuracy);
long long utc_secs = r->fix.timestamp / 1000;
struct tm utc;
gmtime_r((time_t*)&utc_secs, &utc);
D(" - time = %s", asctime(&utc));
#endif
D("Sending fix to callback %p", r->callback);
r->callback(&r->fix);
}
// Parses data received so far and calls reader_call_callback().
static void reader_parse_message(GpsDataReader* r) {
D("Received: '%s'", r->buffer);
int num_read = sscanf(r->buffer, "%lf,%lf,%lf,%f,%f,%f", &r->fix.longitude,
&r->fix.latitude, &r->fix.altitude, &r->fix.bearing,
&r->fix.speed, &r->fix.accuracy);
if (num_read != 6) {
ALOGE("Couldn't find 6 values from the received message %s.", r->buffer);
return;
}
r->fix.flags = DEFAULT_GPS_LOCATION_FLAG;
reader_call_callback(r);
}
// Accepts a newly received string & calls reader_parse_message if '\n' is seen.
static void reader_accept_string(GpsDataReader* r, char* const str,
const int len) {
int index;
for (index = 0; index < len; index++) {
if (r->index >= (int)sizeof(r->buffer) - 1) {
if (str[index] == '\n') {
ALOGW("Message longer than buffer; new byte (%d) skipped.", str[index]);
r->index = 0;
}
} else {
r->buffer[r->index++] = str[index];
if (str[index] == '\n') {
r->buffer[r->index] = '\0';
reader_parse_message(r);
r->index = 0;
}
}
}
}
// GPS state threads which communicates with control and data sockets.
void gps_state_thread(void* arg) {
GpsState* state = (GpsState*)arg;
GpsDataReader reader;
int epoll_fd = epoll_create(2);
int started = -1;
int gps_fd = state->fd;
int control_fd = state->control[1];
memset(&reader, 0, sizeof(reader));
reader.fix.size = sizeof(reader.fix);
epoll_register(epoll_fd, control_fd);
epoll_register(epoll_fd, gps_fd);
while (1) {
struct epoll_event events[2];
int nevents, event_index;
nevents = epoll_wait(epoll_fd, events, 2, 500);
D("Thread received %d events", nevents);
if (nevents < 0) {
if (errno != EINTR)
ALOGE("epoll_wait() unexpected error: %s", strerror(errno));
continue;
} else if (nevents == 0) {
if (started == 1) {
reader_call_callback(&reader);
}
continue;
}
for (event_index = 0; event_index < nevents; event_index++) {
if ((events[event_index].events & (EPOLLERR | EPOLLHUP)) != 0) {
ALOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?");
goto Exit;
}
if ((events[event_index].events & EPOLLIN) != 0) {
int fd = events[event_index].data.fd;
if (fd == control_fd) {
unsigned char cmd = 255;
int ret;
do {
ret = read(fd, &cmd, 1);
} while (ret < 0 && errno == EINTR);
if (cmd == CMD_STOP || cmd == CMD_QUIT) {
if (started == 1) {
D("Thread stopping");
started = 0;
reader.callback = NULL;
}
if (cmd == CMD_QUIT) {
D("Thread quitting");
goto Exit;
}
} else if (cmd == CMD_START) {
if (started != 1) {
reader.callback = state->callbacks.location_cb;
D("Thread starting callback=%p", reader.callback);
reader_call_callback(&reader);
started = 1;
}
} else {
ALOGE("unknown control command %d", cmd);
}
} else if (fd == gps_fd) {
char buff[256];
int ret;
for (;;) {
ret = read(fd, buff, sizeof(buff));
if (ret < 0) {
if (errno == EINTR) continue;
if (errno != EWOULDBLOCK)
ALOGE("error while reading from gps daemon socket: %s:",
strerror(errno));
break;
}
D("Thread received %d bytes: %.*s", ret, ret, buff);
reader_accept_string(&reader, buff, ret);
}
} else {
ALOGE("epoll_wait() returned unknown fd %d.", fd);
}
}
}
}
Exit:
epoll_deregister(epoll_fd, control_fd);
epoll_deregister(epoll_fd, gps_fd);
}