CoreLocationLocationProvider.mm (7303B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "CoreLocationLocationProvider.h" 8 #include "GeolocationPosition.h" 9 #include "MLSFallback.h" 10 #include "mozilla/FloatingPoint.h" 11 #include "mozilla/UniquePtr.h" 12 #include "mozilla/dom/GeolocationPositionErrorBinding.h" 13 #include "mozilla/glean/DomGeolocationMetrics.h" 14 #include "nsCOMPtr.h" 15 #include "nsIConsoleService.h" 16 #include "nsServiceManagerUtils.h" 17 #include "prtime.h" 18 19 #include <CoreLocation/CLError.h> 20 #include <CoreLocation/CLLocation.h> 21 #include <CoreLocation/CLLocationManager.h> 22 #include <CoreLocation/CLLocationManagerDelegate.h> 23 24 #include <objc/objc-runtime.h> 25 #include <objc/objc.h> 26 27 #include "nsObjCExceptions.h" 28 29 using namespace mozilla; 30 31 #define kDefaultAccuracy kCLLocationAccuracyNearestTenMeters 32 33 @interface LocationDelegate : NSObject <CLLocationManagerDelegate> { 34 CoreLocationLocationProvider* mProvider; 35 } 36 37 - (id)init:(CoreLocationLocationProvider*)aProvider; 38 - (void)locationManager:(CLLocationManager*)aManager 39 didFailWithError:(NSError*)aError; 40 - (void)locationManager:(CLLocationManager*)aManager 41 didUpdateLocations:(NSArray*)locations; 42 43 @end 44 45 @implementation LocationDelegate 46 - (id)init:(CoreLocationLocationProvider*)aProvider { 47 if ((self = [super init])) { 48 mProvider = aProvider; 49 } 50 51 return self; 52 } 53 54 - (void)locationManager:(CLLocationManager*)aManager 55 didFailWithError:(NSError*)aError { 56 nsCOMPtr<nsIConsoleService> console = 57 do_GetService(NS_CONSOLESERVICE_CONTRACTID); 58 59 NS_ENSURE_TRUE_VOID(console); 60 61 NSString* message = [@"Failed to acquire position: " 62 stringByAppendingString:[aError localizedDescription]]; 63 64 console->LogStringMessage(NS_ConvertUTF8toUTF16([message UTF8String]).get()); 65 66 // The CL provider does not fallback to GeoIP, so use 67 // NetworkGeolocationProvider for this. The concept here is: on error, hand 68 // off geolocation to MLS, which will then report back a location or error. 69 mProvider->CreateMLSFallbackProvider(); 70 } 71 72 - (void)locationManager:(CLLocationManager*)aManager 73 didUpdateLocations:(NSArray*)aLocations { 74 if (aLocations.count < 1) { 75 return; 76 } 77 78 mProvider->CancelMLSFallbackProvider(); 79 80 CLLocation* location = [aLocations objectAtIndex:0]; 81 82 double altitude; 83 double altitudeAccuracy; 84 85 // A negative verticalAccuracy indicates that the altitude value is invalid. 86 if (location.verticalAccuracy >= 0) { 87 altitude = location.altitude; 88 altitudeAccuracy = location.verticalAccuracy; 89 } else { 90 altitude = UnspecifiedNaN<double>(); 91 altitudeAccuracy = UnspecifiedNaN<double>(); 92 } 93 94 double speed = 95 location.speed >= 0 ? location.speed : UnspecifiedNaN<double>(); 96 97 double heading = 98 location.course >= 0 ? location.course : UnspecifiedNaN<double>(); 99 100 // nsGeoPositionCoords will convert NaNs to null for optional properties of 101 // the JavaScript Coordinates object. 102 nsCOMPtr<nsIDOMGeoPosition> geoPosition = new nsGeoPosition( 103 location.coordinate.latitude, location.coordinate.longitude, altitude, 104 location.horizontalAccuracy, altitudeAccuracy, heading, speed, 105 PR_Now() / PR_USEC_PER_MSEC); 106 107 if (!mProvider->IsEverUpdated()) { 108 // Saw signal without MLS fallback 109 glean::geolocation::fallback 110 .EnumGet(glean::geolocation::FallbackLabel::eNone) 111 .Add(); 112 } 113 114 mProvider->Update(geoPosition); 115 } 116 @end 117 118 NS_IMPL_ISUPPORTS(CoreLocationLocationProvider::MLSUpdate, 119 nsIGeolocationUpdate); 120 121 CoreLocationLocationProvider::MLSUpdate::MLSUpdate( 122 CoreLocationLocationProvider& parentProvider) 123 : mParentLocationProvider(parentProvider) {} 124 125 NS_IMETHODIMP 126 CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition* position) { 127 nsCOMPtr<nsIDOMGeoPositionCoords> coords; 128 position->GetCoords(getter_AddRefs(coords)); 129 if (!coords) { 130 return NS_ERROR_FAILURE; 131 } 132 mParentLocationProvider.Update(position); 133 return NS_OK; 134 } 135 136 NS_IMETHODIMP 137 CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error) { 138 mParentLocationProvider.NotifyError(error); 139 return NS_OK; 140 } 141 142 class CoreLocationObjects { 143 public: 144 nsresult Init(CoreLocationLocationProvider* aProvider) { 145 mLocationManager = [[CLLocationManager alloc] init]; 146 NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE); 147 148 mLocationDelegate = [[LocationDelegate alloc] init:aProvider]; 149 NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE); 150 151 mLocationManager.desiredAccuracy = kDefaultAccuracy; 152 mLocationManager.delegate = mLocationDelegate; 153 154 return NS_OK; 155 } 156 157 ~CoreLocationObjects() { 158 if (mLocationManager) { 159 [mLocationManager release]; 160 } 161 162 if (mLocationDelegate) { 163 [mLocationDelegate release]; 164 } 165 } 166 167 LocationDelegate* mLocationDelegate; 168 CLLocationManager* mLocationManager; 169 }; 170 171 NS_IMPL_ISUPPORTS(CoreLocationLocationProvider, nsIGeolocationProvider) 172 173 CoreLocationLocationProvider::CoreLocationLocationProvider() 174 : mCLObjects(nullptr), mMLSFallbackProvider(nullptr) {} 175 176 NS_IMETHODIMP 177 CoreLocationLocationProvider::Startup() { 178 if (!mCLObjects) { 179 auto clObjs = MakeUnique<CoreLocationObjects>(); 180 181 nsresult rv = clObjs->Init(this); 182 NS_ENSURE_SUCCESS(rv, rv); 183 184 mCLObjects = clObjs.release(); 185 } 186 187 // Must be stopped before starting or response (success or failure) is not 188 // guaranteed 189 [mCLObjects->mLocationManager stopUpdatingLocation]; 190 [mCLObjects->mLocationManager startUpdatingLocation]; 191 return NS_OK; 192 } 193 194 NS_IMETHODIMP 195 CoreLocationLocationProvider::Watch(nsIGeolocationUpdate* aCallback) { 196 if (mCallback) { 197 return NS_OK; 198 } 199 200 mCallback = aCallback; 201 return NS_OK; 202 } 203 204 NS_IMETHODIMP 205 CoreLocationLocationProvider::Shutdown() { 206 NS_ENSURE_STATE(mCLObjects); 207 208 [mCLObjects->mLocationManager stopUpdatingLocation]; 209 210 delete mCLObjects; 211 mCLObjects = nullptr; 212 213 if (mMLSFallbackProvider) { 214 mMLSFallbackProvider->Shutdown( 215 MLSFallback::ShutdownReason::ProviderShutdown); 216 mMLSFallbackProvider = nullptr; 217 } 218 219 return NS_OK; 220 } 221 222 NS_IMETHODIMP 223 CoreLocationLocationProvider::SetHighAccuracy(bool aEnable) { 224 NS_ENSURE_STATE(mCLObjects); 225 226 mCLObjects->mLocationManager.desiredAccuracy = 227 aEnable ? kCLLocationAccuracyBest : kDefaultAccuracy; 228 229 return NS_OK; 230 } 231 232 void CoreLocationLocationProvider::Update(nsIDOMGeoPosition* aSomewhere) { 233 if (aSomewhere && mCallback) { 234 mCallback->Update(aSomewhere); 235 } 236 mEverUpdated = true; 237 } 238 void CoreLocationLocationProvider::NotifyError(uint16_t aErrorCode) { 239 nsCOMPtr<nsIGeolocationUpdate> callback(mCallback); 240 callback->NotifyError(aErrorCode); 241 } 242 void CoreLocationLocationProvider::CreateMLSFallbackProvider() { 243 if (mMLSFallbackProvider) { 244 return; 245 } 246 247 mMLSFallbackProvider = new MLSFallback(0); 248 mMLSFallbackProvider->Startup(new MLSUpdate(*this)); 249 } 250 251 void CoreLocationLocationProvider::CancelMLSFallbackProvider() { 252 if (!mMLSFallbackProvider) { 253 return; 254 } 255 256 mMLSFallbackProvider->Shutdown( 257 MLSFallback::ShutdownReason::ProviderResponded); 258 mMLSFallbackProvider = nullptr; 259 }