blob: 5d108a38a08209d8b33a856c24a92d418dee4c7a [file] [log] [blame]
// 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;
}