зеркало из https://github.com/mozilla/gecko-dev.git
468 строки
10 KiB
C++
468 строки
10 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "GpsdLocationProvider.h"
|
|
#include <errno.h>
|
|
#include <gps.h>
|
|
#include "MLSFallback.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/FloatingPoint.h"
|
|
#include "mozilla/LazyIdleThread.h"
|
|
#include "mozilla/dom/PositionErrorBinding.h"
|
|
#include "nsGeoPosition.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
//
|
|
// MLSGeolocationUpdate
|
|
//
|
|
|
|
/**
|
|
* |MLSGeolocationUpdate| provides a fallback if gpsd is not supported.
|
|
*/
|
|
class GpsdLocationProvider::MLSGeolocationUpdate final
|
|
: public nsIGeolocationUpdate
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIGEOLOCATIONUPDATE
|
|
|
|
explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
|
|
|
|
protected:
|
|
~MLSGeolocationUpdate() = default;
|
|
|
|
private:
|
|
nsCOMPtr<nsIGeolocationUpdate> mCallback;
|
|
};
|
|
|
|
GpsdLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
|
|
nsIGeolocationUpdate* aCallback)
|
|
: mCallback(aCallback)
|
|
{
|
|
MOZ_ASSERT(mCallback);
|
|
}
|
|
|
|
// nsISupports
|
|
//
|
|
|
|
NS_IMPL_ISUPPORTS(GpsdLocationProvider::MLSGeolocationUpdate, nsIGeolocationUpdate);
|
|
|
|
// nsIGeolocationUpdate
|
|
//
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::MLSGeolocationUpdate::Update(nsIDOMGeoPosition* aPosition)
|
|
{
|
|
nsCOMPtr<nsIDOMGeoPositionCoords> coords;
|
|
aPosition->GetCoords(getter_AddRefs(coords));
|
|
if (!coords) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return mCallback->Update(aPosition);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError)
|
|
{
|
|
return mCallback->NotifyError(aError);
|
|
}
|
|
|
|
//
|
|
// UpdateRunnable
|
|
//
|
|
|
|
class GpsdLocationProvider::UpdateRunnable final : public Runnable
|
|
{
|
|
public:
|
|
UpdateRunnable(
|
|
const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
|
|
nsIDOMGeoPosition* aPosition)
|
|
: mLocationProvider(aLocationProvider)
|
|
, mPosition(aPosition)
|
|
{
|
|
MOZ_ASSERT(mLocationProvider);
|
|
MOZ_ASSERT(mPosition);
|
|
}
|
|
|
|
// nsIRunnable
|
|
//
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
mLocationProvider->Update(mPosition);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
|
|
RefPtr<nsIDOMGeoPosition> mPosition;
|
|
};
|
|
|
|
//
|
|
// NotifyErrorRunnable
|
|
//
|
|
|
|
class GpsdLocationProvider::NotifyErrorRunnable final : public Runnable
|
|
{
|
|
public:
|
|
NotifyErrorRunnable(
|
|
const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
|
|
int aError)
|
|
: mLocationProvider(aLocationProvider)
|
|
, mError(aError)
|
|
{
|
|
MOZ_ASSERT(mLocationProvider);
|
|
}
|
|
|
|
// nsIRunnable
|
|
//
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
mLocationProvider->NotifyError(mError);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
|
|
int mError;
|
|
};
|
|
|
|
//
|
|
// PollRunnable
|
|
//
|
|
|
|
/**
|
|
* |PollRunnable| does the main work of processing GPS data received
|
|
* from gpsd. libgps blocks while polling, so this runnable has to be
|
|
* executed on it's own thread. To cancel the poll runnable, invoke
|
|
* |StopRunning| and |PollRunnable| will stop within a reasonable time
|
|
* frame.
|
|
*/
|
|
class GpsdLocationProvider::PollRunnable final : public Runnable
|
|
{
|
|
public:
|
|
PollRunnable(
|
|
const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider)
|
|
: mLocationProvider(aLocationProvider)
|
|
, mRunning(true)
|
|
{
|
|
MOZ_ASSERT(mLocationProvider);
|
|
}
|
|
|
|
static bool IsSupported()
|
|
{
|
|
return GPSD_API_MAJOR_VERSION == 5;
|
|
}
|
|
|
|
bool IsRunning() const
|
|
{
|
|
return mRunning;
|
|
}
|
|
|
|
void StopRunning()
|
|
{
|
|
mRunning = false;
|
|
}
|
|
|
|
// nsIRunnable
|
|
//
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
int err;
|
|
|
|
switch (GPSD_API_MAJOR_VERSION) {
|
|
case 5:
|
|
err = PollLoop5();
|
|
break;
|
|
default:
|
|
err = PositionError_Binding::POSITION_UNAVAILABLE;
|
|
break;
|
|
}
|
|
|
|
if (err) {
|
|
NS_DispatchToMainThread(
|
|
MakeAndAddRef<NotifyErrorRunnable>(mLocationProvider, err));
|
|
}
|
|
|
|
mLocationProvider = nullptr;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
int PollLoop5()
|
|
{
|
|
#if GPSD_API_MAJOR_VERSION == 5
|
|
static const int GPSD_WAIT_TIMEOUT_US = 1000000; /* us to wait for GPS data */
|
|
|
|
struct gps_data_t gpsData;
|
|
|
|
auto res = gps_open(nullptr, nullptr, &gpsData);
|
|
|
|
if (res < 0) {
|
|
return ErrnoToError(errno);
|
|
}
|
|
|
|
gps_stream(&gpsData, WATCH_ENABLE | WATCH_JSON, NULL);
|
|
|
|
int err = 0;
|
|
|
|
// nsGeoPositionCoords will convert NaNs to null for optional properties of
|
|
// the JavaScript Coordinates object.
|
|
double lat = 0;
|
|
double lon = 0;
|
|
double alt = UnspecifiedNaN<double>();
|
|
double hError = 0;
|
|
double vError = UnspecifiedNaN<double>();
|
|
double heading = UnspecifiedNaN<double>();
|
|
double speed = UnspecifiedNaN<double>();
|
|
|
|
while (IsRunning()) {
|
|
|
|
errno = 0;
|
|
auto hasGpsData = gps_waiting(&gpsData, GPSD_WAIT_TIMEOUT_US);
|
|
|
|
if (errno) {
|
|
err = ErrnoToError(errno);
|
|
break;
|
|
}
|
|
if (!hasGpsData) {
|
|
continue; /* woke up from timeout */
|
|
}
|
|
|
|
res = gps_read(&gpsData);
|
|
|
|
if (res < 0) {
|
|
err = ErrnoToError(errno);
|
|
break;
|
|
} else if (!res) {
|
|
continue; /* no data available */
|
|
}
|
|
|
|
if (gpsData.status == STATUS_NO_FIX) {
|
|
continue;
|
|
}
|
|
|
|
switch (gpsData.fix.mode) {
|
|
case MODE_3D:
|
|
if (!IsNaN(gpsData.fix.altitude)) {
|
|
alt = gpsData.fix.altitude;
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
case MODE_2D:
|
|
if (!IsNaN(gpsData.fix.latitude)) {
|
|
lat = gpsData.fix.latitude;
|
|
}
|
|
if (!IsNaN(gpsData.fix.longitude)) {
|
|
lon = gpsData.fix.longitude;
|
|
}
|
|
if (!IsNaN(gpsData.fix.epx) && !IsNaN(gpsData.fix.epy)) {
|
|
hError = std::max(gpsData.fix.epx, gpsData.fix.epy);
|
|
} else if (!IsNaN(gpsData.fix.epx)) {
|
|
hError = gpsData.fix.epx;
|
|
} else if (!IsNaN(gpsData.fix.epy)) {
|
|
hError = gpsData.fix.epy;
|
|
}
|
|
if (!IsNaN(gpsData.fix.altitude)) {
|
|
alt = gpsData.fix.altitude;
|
|
}
|
|
if (!IsNaN(gpsData.fix.epv)) {
|
|
vError = gpsData.fix.epv;
|
|
}
|
|
if (!IsNaN(gpsData.fix.track)) {
|
|
heading = gpsData.fix.track;
|
|
}
|
|
if (!IsNaN(gpsData.fix.speed)) {
|
|
speed = gpsData.fix.speed;
|
|
}
|
|
break;
|
|
default:
|
|
continue; // There's no useful data in this fix; continue.
|
|
}
|
|
|
|
NS_DispatchToMainThread(
|
|
MakeAndAddRef<UpdateRunnable>(mLocationProvider,
|
|
new nsGeoPosition(lat, lon, alt,
|
|
hError, vError,
|
|
heading, speed,
|
|
PR_Now() / PR_USEC_PER_MSEC)));
|
|
}
|
|
|
|
gps_stream(&gpsData, WATCH_DISABLE, NULL);
|
|
gps_close(&gpsData);
|
|
|
|
return err;
|
|
#else
|
|
return PositionError_Binding::POSITION_UNAVAILABLE;
|
|
#endif // GPSD_MAJOR_API_VERSION
|
|
}
|
|
|
|
static int ErrnoToError(int aErrno)
|
|
{
|
|
switch (aErrno) {
|
|
case EACCES:
|
|
MOZ_FALLTHROUGH;
|
|
case EPERM:
|
|
MOZ_FALLTHROUGH;
|
|
case EROFS:
|
|
return PositionError_Binding::PERMISSION_DENIED;
|
|
case ETIME:
|
|
MOZ_FALLTHROUGH;
|
|
case ETIMEDOUT:
|
|
return PositionError_Binding::TIMEOUT;
|
|
default:
|
|
return PositionError_Binding::POSITION_UNAVAILABLE;
|
|
}
|
|
}
|
|
|
|
private:
|
|
nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
|
|
Atomic<bool> mRunning;
|
|
};
|
|
|
|
//
|
|
// GpsdLocationProvider
|
|
//
|
|
|
|
const uint32_t GpsdLocationProvider::GPSD_POLL_THREAD_TIMEOUT_MS = 5000;
|
|
|
|
GpsdLocationProvider::GpsdLocationProvider()
|
|
{ }
|
|
|
|
GpsdLocationProvider::~GpsdLocationProvider()
|
|
{ }
|
|
|
|
void
|
|
GpsdLocationProvider::Update(nsIDOMGeoPosition* aPosition)
|
|
{
|
|
if (!mCallback || !mPollRunnable) {
|
|
return; // not initialized or already shut down
|
|
}
|
|
|
|
if (mMLSProvider) {
|
|
/* We got a location from gpsd, so let's cancel our MLS fallback. */
|
|
mMLSProvider->Shutdown();
|
|
mMLSProvider = nullptr;
|
|
}
|
|
|
|
mCallback->Update(aPosition);
|
|
}
|
|
|
|
void
|
|
GpsdLocationProvider::NotifyError(int aError)
|
|
{
|
|
if (!mCallback) {
|
|
return; // not initialized or already shut down
|
|
}
|
|
|
|
if (!mMLSProvider) {
|
|
/* With gpsd failed, we restart MLS. It will be canceled once we
|
|
* get another location from gpsd.
|
|
*/
|
|
mMLSProvider = MakeAndAddRef<MLSFallback>();
|
|
mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
|
|
}
|
|
|
|
mCallback->NotifyError(aError);
|
|
}
|
|
|
|
// nsISupports
|
|
//
|
|
|
|
NS_IMPL_ISUPPORTS(GpsdLocationProvider, nsIGeolocationProvider)
|
|
|
|
// nsIGeolocationProvider
|
|
//
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::Startup()
|
|
{
|
|
if (!PollRunnable::IsSupported()) {
|
|
return NS_OK; // We'll fall back to MLS.
|
|
}
|
|
|
|
if (mPollRunnable) {
|
|
return NS_OK; // already running
|
|
}
|
|
|
|
RefPtr<PollRunnable> pollRunnable =
|
|
MakeAndAddRef<PollRunnable>(
|
|
nsMainThreadPtrHandle<GpsdLocationProvider>(
|
|
new nsMainThreadPtrHolder<GpsdLocationProvider>(this)));
|
|
|
|
// Use existing poll thread...
|
|
RefPtr<LazyIdleThread> pollThread = mPollThread;
|
|
|
|
// ... or create a new one.
|
|
if (!pollThread) {
|
|
pollThread = MakeAndAddRef<LazyIdleThread>(
|
|
GPSD_POLL_THREAD_TIMEOUT_MS,
|
|
NS_LITERAL_CSTRING("Gpsd poll thread"),
|
|
LazyIdleThread::ManualShutdown);
|
|
}
|
|
|
|
auto rv = pollThread->Dispatch(pollRunnable, NS_DISPATCH_NORMAL);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
mPollRunnable = pollRunnable.forget();
|
|
mPollThread = pollThread.forget();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::Watch(nsIGeolocationUpdate* aCallback)
|
|
{
|
|
mCallback = aCallback;
|
|
|
|
/* The MLS fallback will kick in after a few seconds if gpsd
|
|
* doesn't provide location information within time. Once we
|
|
* see the first message from gpsd, the fallback will be
|
|
* disabled in |Update|.
|
|
*/
|
|
mMLSProvider = MakeAndAddRef<MLSFallback>();
|
|
mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::Shutdown()
|
|
{
|
|
if (mMLSProvider) {
|
|
mMLSProvider->Shutdown();
|
|
mMLSProvider = nullptr;
|
|
}
|
|
|
|
if (!mPollRunnable) {
|
|
return NS_OK; // not running
|
|
}
|
|
|
|
mPollRunnable->StopRunning();
|
|
mPollRunnable = nullptr;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GpsdLocationProvider::SetHighAccuracy(bool aHigh)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|