diff options
Diffstat (limited to 'dom/system/linux/GpsdLocationProvider.cpp')
-rw-r--r-- | dom/system/linux/GpsdLocationProvider.cpp | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/dom/system/linux/GpsdLocationProvider.cpp b/dom/system/linux/GpsdLocationProvider.cpp new file mode 100644 index 000000000..eeee806dc --- /dev/null +++ b/dom/system/linux/GpsdLocationProvider.cpp @@ -0,0 +1,465 @@ +/* -*- 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 "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 |