aa84373e创建于 2012年6月9日历史提交
// 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;
}