Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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
#include "nsCOMPtr.h"
#include "GeolocationPosition.h"
#include "nsIConsoleService.h"
#include "nsServiceManagerUtils.h"
#include "CoreLocationLocationProvider.h"
#include "prtime.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/GeolocationPositionErrorBinding.h"
#include "mozilla/glean/GleanMetrics.h"
#include "MLSFallback.h"
#include <CoreLocation/CLError.h>
#include <CoreLocation/CLLocation.h>
#include <CoreLocation/CLLocationManager.h>
#include <CoreLocation/CLLocationManagerDelegate.h>
#include <objc/objc.h>
#include <objc/objc-runtime.h>
#include "nsObjCExceptions.h"
using namespace mozilla;
MOZ_RUNINIT static const CLLocationAccuracy kHIGH_ACCURACY =
kCLLocationAccuracyBest;
MOZ_RUNINIT static const CLLocationAccuracy kDEFAULT_ACCURACY =
kCLLocationAccuracyNearestTenMeters;
@interface LocationDelegate : NSObject <CLLocationManagerDelegate> {
CoreLocationLocationProvider* mProvider;
}
- (id)init:(CoreLocationLocationProvider*)aProvider;
- (void)locationManager:(CLLocationManager*)aManager
didFailWithError:(NSError*)aError;
- (void)locationManager:(CLLocationManager*)aManager
didUpdateLocations:(NSArray*)locations;
@end
@implementation LocationDelegate
- (id)init:(CoreLocationLocationProvider*)aProvider {
if ((self = [super init])) {
mProvider = aProvider;
}
return self;
}
- (void)locationManager:(CLLocationManager*)aManager
didFailWithError:(NSError*)aError {
nsCOMPtr<nsIConsoleService> console =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(console);
NSString* message = [@"Failed to acquire position: "
stringByAppendingString:[aError localizedDescription]];
console->LogStringMessage(NS_ConvertUTF8toUTF16([message UTF8String]).get());
// The CL provider does not fallback to GeoIP, so use
// NetworkGeolocationProvider for this. The concept here is: on error, hand
// off geolocation to MLS, which will then report back a location or error.
mProvider->CreateMLSFallbackProvider();
}
- (void)locationManager:(CLLocationManager*)aManager
didUpdateLocations:(NSArray*)aLocations {
if (aLocations.count < 1) {
return;
}
mProvider->CancelMLSFallbackProvider();
CLLocation* location = [aLocations objectAtIndex:0];
double altitude;
double altitudeAccuracy;
// A negative verticalAccuracy indicates that the altitude value is invalid.
if (location.verticalAccuracy >= 0) {
altitude = location.altitude;
altitudeAccuracy = location.verticalAccuracy;
} else {
altitude = UnspecifiedNaN<double>();
altitudeAccuracy = UnspecifiedNaN<double>();
}
double speed =
location.speed >= 0 ? location.speed : UnspecifiedNaN<double>();
double heading =
location.course >= 0 ? location.course : UnspecifiedNaN<double>();
// nsGeoPositionCoords will convert NaNs to null for optional properties of
// the JavaScript Coordinates object.
nsCOMPtr<nsIDOMGeoPosition> geoPosition = new nsGeoPosition(
location.coordinate.latitude, location.coordinate.longitude, altitude,
location.horizontalAccuracy, altitudeAccuracy, heading, speed,
PR_Now() / PR_USEC_PER_MSEC);
if (!mProvider->IsEverUpdated()) {
// Saw signal without MLS fallback
glean::geolocation::fallback
.EnumGet(glean::geolocation::FallbackLabel::eNone)
.Add();
}
mProvider->Update(geoPosition);
Telemetry::Accumulate(Telemetry::GEOLOCATION_OSX_SOURCE_IS_MLS, false);
}
@end
NS_IMPL_ISUPPORTS(CoreLocationLocationProvider::MLSUpdate,
nsIGeolocationUpdate);
CoreLocationLocationProvider::MLSUpdate::MLSUpdate(
CoreLocationLocationProvider& parentProvider)
: mParentLocationProvider(parentProvider) {}
NS_IMETHODIMP
CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition* position) {
nsCOMPtr<nsIDOMGeoPositionCoords> coords;
position->GetCoords(getter_AddRefs(coords));
if (!coords) {
return NS_ERROR_FAILURE;
}
mParentLocationProvider.Update(position);
Telemetry::Accumulate(Telemetry::GEOLOCATION_OSX_SOURCE_IS_MLS, true);
return NS_OK;
}
NS_IMETHODIMP
CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error) {
mParentLocationProvider.NotifyError(error);
return NS_OK;
}
class CoreLocationObjects {
public:
nsresult Init(CoreLocationLocationProvider* aProvider) {
mLocationManager = [[CLLocationManager alloc] init];
NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE);
mLocationDelegate = [[LocationDelegate alloc] init:aProvider];
NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE);
mLocationManager.desiredAccuracy = kDEFAULT_ACCURACY;
mLocationManager.delegate = mLocationDelegate;
return NS_OK;
}
~CoreLocationObjects() {
if (mLocationManager) {
[mLocationManager release];
}
if (mLocationDelegate) {
[mLocationDelegate release];
}
}
LocationDelegate* mLocationDelegate;
CLLocationManager* mLocationManager;
};
NS_IMPL_ISUPPORTS(CoreLocationLocationProvider, nsIGeolocationProvider)
CoreLocationLocationProvider::CoreLocationLocationProvider()
: mCLObjects(nullptr), mMLSFallbackProvider(nullptr) {}
NS_IMETHODIMP
CoreLocationLocationProvider::Startup() {
if (!mCLObjects) {
auto clObjs = MakeUnique<CoreLocationObjects>();
nsresult rv = clObjs->Init(this);
NS_ENSURE_SUCCESS(rv, rv);
mCLObjects = clObjs.release();
}
// Must be stopped before starting or response (success or failure) is not
// guaranteed
[mCLObjects->mLocationManager stopUpdatingLocation];
[mCLObjects->mLocationManager startUpdatingLocation];
return NS_OK;
}
NS_IMETHODIMP
CoreLocationLocationProvider::Watch(nsIGeolocationUpdate* aCallback) {
if (mCallback) {
return NS_OK;
}
mCallback = aCallback;
return NS_OK;
}
NS_IMETHODIMP
CoreLocationLocationProvider::Shutdown() {
NS_ENSURE_STATE(mCLObjects);
[mCLObjects->mLocationManager stopUpdatingLocation];
delete mCLObjects;
mCLObjects = nullptr;
if (mMLSFallbackProvider) {
mMLSFallbackProvider->Shutdown(
MLSFallback::ShutdownReason::ProviderShutdown);
mMLSFallbackProvider = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP
CoreLocationLocationProvider::SetHighAccuracy(bool aEnable) {
NS_ENSURE_STATE(mCLObjects);
mCLObjects->mLocationManager.desiredAccuracy =
(aEnable ? kHIGH_ACCURACY : kDEFAULT_ACCURACY);
return NS_OK;
}
void CoreLocationLocationProvider::Update(nsIDOMGeoPosition* aSomewhere) {
if (aSomewhere && mCallback) {
mCallback->Update(aSomewhere);
}
mEverUpdated = true;
}
void CoreLocationLocationProvider::NotifyError(uint16_t aErrorCode) {
nsCOMPtr<nsIGeolocationUpdate> callback(mCallback);
callback->NotifyError(aErrorCode);
}
void CoreLocationLocationProvider::CreateMLSFallbackProvider() {
if (mMLSFallbackProvider) {
return;
}
mMLSFallbackProvider = new MLSFallback(0);
mMLSFallbackProvider->Startup(new MLSUpdate(*this));
}
void CoreLocationLocationProvider::CancelMLSFallbackProvider() {
if (!mMLSFallbackProvider) {
return;
}
mMLSFallbackProvider->Shutdown(
MLSFallback::ShutdownReason::ProviderResponded);
mMLSFallbackProvider = nullptr;
}