Mypal/dom/system/linux/GpsdLocationProvider.cpp
2021-02-04 16:48:36 +02:00

465 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsGeoPosition.h"
#include "nsIDOMGeoPositionError.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 = nsIDOMGeoPositionError::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;
double lat = -1;
double lon = -1;
double alt = -1;
double hError = -1;
double vError = -1;
double heading = -1;
double speed = -1;
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 nsIDOMGeoPositionError::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 nsIDOMGeoPositionError::PERMISSION_DENIED;
case ETIME:
MOZ_FALLTHROUGH;
case ETIMEDOUT:
return nsIDOMGeoPositionError::TIMEOUT;
default:
return nsIDOMGeoPositionError::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