summaryrefslogtreecommitdiffstats
path: root/content/browser/geolocation/libgps_wrapper_linux.cc
blob: 5d108a38a08209d8b33a856c24a92d418dee4c7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/geolocation/libgps_wrapper_linux.h"

#include <math.h>
#include <dlfcn.h>
#include <errno.h>

#include "base/logging.h"
#include "base/stringprintf.h"
#include "content/public/common/geoposition.h"
#include "third_party/gpsd/release-3.1/gps.h"

COMPILE_ASSERT(GPSD_API_MAJOR_VERSION == 5, GPSD_API_version_is_not_5);

namespace {
const char kLibGpsName[] = "libgps.so.20";
}  // namespace

LibGps::LibGps(void* dl_handle,
               gps_open_fn gps_open,
               gps_close_fn gps_close,
               gps_read_fn gps_read)
    : dl_handle_(dl_handle),
      gps_open_(gps_open),
      gps_close_(gps_close),
      gps_read_(gps_read),
      gps_data_(new gps_data_t),
      is_open_(false) {
  DCHECK(gps_open_);
  DCHECK(gps_close_);
  DCHECK(gps_read_);
}

LibGps::~LibGps() {
  Stop();
  if (dl_handle_) {
    const int err = dlclose(dl_handle_);
    DCHECK_EQ(0, err) << "Error closing dl handle: " << err;
  }
}

LibGps* LibGps::New() {
  void* dl_handle = dlopen(kLibGpsName, RTLD_LAZY);
  if (!dl_handle) {
    DLOG(WARNING) << "Could not open " << kLibGpsName << ": " << dlerror();
    return NULL;
  }

  DLOG(INFO) << "Loaded " << kLibGpsName;

  #define DECLARE_FN_POINTER(function)                                   \
    function##_fn function = reinterpret_cast<function##_fn>(            \
        dlsym(dl_handle, #function));                                    \
    if (!function) {                                                     \
      DLOG(WARNING) << "libgps " << #function << " error: " << dlerror(); \
      dlclose(dl_handle);                                                \
      return NULL;                                                       \
    }
  DECLARE_FN_POINTER(gps_open);
  DECLARE_FN_POINTER(gps_close);
  DECLARE_FN_POINTER(gps_read);
  // We don't use gps_shm_read() directly, just to make sure that libgps has
  // the shared memory support.
  typedef int (*gps_shm_read_fn)(struct gps_data_t*);
  DECLARE_FN_POINTER(gps_shm_read);
  #undef DECLARE_FN_POINTER

  return new LibGps(dl_handle, gps_open, gps_close, gps_read);
}

bool LibGps::Start() {
  if (is_open_)
    return true;

#if defined(OS_CHROMEOS)
  errno = 0;
  if (gps_open_(GPSD_SHARED_MEMORY, 0, gps_data_.get()) != 0) {
    // See gps.h NL_NOxxx for definition of gps_open() error numbers.
    DLOG(WARNING) << "gps_open() failed " << errno;
    return false;
  } else {
    is_open_ = true;
    return true;
  }
#else  // drop the support for desktop linux for now
  DLOG(WARNING) << "LibGps is only supported on ChromeOS";
  return false;
#endif
}
void LibGps::Stop() {
  if (is_open_)
    gps_close_(gps_data_.get());
  is_open_ = false;
}

bool LibGps::Read(content::Geoposition* position) {
  DCHECK(position);
  position->error_code = content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
  if (!is_open_) {
      DLOG(WARNING) << "No gpsd connection";
      position->error_message = "No gpsd connection";
      return false;
  }

  if (gps_read_(gps_data_.get()) < 0) {
      DLOG(WARNING) << "gps_read() fails";
      position->error_message = "gps_read() fails";
      return false;
  }

  if (!GetPositionIfFixed(position)) {
      DLOG(WARNING) << "No fixed position";
      position->error_message = "No fixed position";
      return false;
  }

  position->error_code = content::Geoposition::ERROR_CODE_NONE;
  position->timestamp = base::Time::Now();
  if (!position->Validate()) {
    // GetPositionIfFixed returned true, yet we've not got a valid fix.
    // This shouldn't happen; something went wrong in the conversion.
    NOTREACHED() << "Invalid position from GetPositionIfFixed: lat,long "
                 << position->latitude << "," << position->longitude
                 << " accuracy " << position->accuracy << " time "
                 << position->timestamp.ToDoubleT();
    position->error_code =
        content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
    position->error_message = "Bad fix from gps";
    return false;
  }
  return true;
}

bool LibGps::GetPositionIfFixed(content::Geoposition* position) {
  DCHECK(position);
  if (gps_data_->status == STATUS_NO_FIX) {
    DVLOG(2) << "Status_NO_FIX";
    return false;
  }

  if (isnan(gps_data_->fix.latitude) || isnan(gps_data_->fix.longitude)) {
    DVLOG(2) << "No valid lat/lon value";
    return false;
  }

  position->latitude = gps_data_->fix.latitude;
  position->longitude = gps_data_->fix.longitude;

  if (!isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
    position->accuracy = std::max(gps_data_->fix.epx, gps_data_->fix.epy);
  } else if (isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
    position->accuracy = gps_data_->fix.epy;
  } else if (!isnan(gps_data_->fix.epx) && isnan(gps_data_->fix.epy)) {
    position->accuracy = gps_data_->fix.epx;
  } else {
    // TODO(joth): Fixme. This is a workaround for http://crbug.com/99326
    DVLOG(2) << "libgps reported accuracy NaN, forcing to zero";
    position->accuracy = 0;
  }

  if (gps_data_->fix.mode == MODE_3D && !isnan(gps_data_->fix.altitude)) {
    position->altitude = gps_data_->fix.altitude;
    if (!isnan(gps_data_->fix.epv))
      position->altitude_accuracy = gps_data_->fix.epv;
  }

  if (!isnan(gps_data_->fix.track))
    position->heading = gps_data_->fix.track;
  if (!isnan(gps_data_->fix.speed))
    position->speed = gps_data_->fix.speed;
  return true;
}