1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // This file contains the class definitions for the CoreLocation data provider 6 // class and the accompanying Objective C wrapper class. This data provider 7 // is used to allow the CoreLocation wrapper to run on the UI thread, since 8 // CLLocationManager's start and stop updating methods must be called from a 9 // thread with an active NSRunLoop. Currently only the UI thread appears to 10 // fill that requirement. 11 12 #include "content/browser/geolocation/core_location_data_provider_mac.h" 13 14 #include "base/bind.h" 15 #include "base/logging.h" 16 #include "base/time/time.h" 17 #include "content/browser/geolocation/core_location_provider_mac.h" 18 #include "content/browser/geolocation/geolocation_provider_impl.h" 19 20 using content::CoreLocationDataProviderMac; 21 using content::Geoposition; 22 23 // A few required declarations since the CoreLocation headers are not available 24 // with the Mac OS X 10.5 SDK. 25 // TODO(jorgevillatoro): Remove these declarations when we build against 10.6 26 27 // This idea was borrowed from wifi_data_provider_corewlan_mac.mm 28 typedef double CLLocationDegrees; 29 typedef double CLLocationAccuracy; 30 typedef double CLLocationSpeed; 31 typedef double CLLocationDirection; 32 typedef double CLLocationDistance; 33 typedef struct { 34 CLLocationDegrees latitude; 35 CLLocationDegrees longitude; 36 } CLLocationCoordinate2D; 37 38 enum { 39 kCLErrorLocationUnknown = 0, 40 kCLErrorDenied 41 }; 42 43 @interface CLLocationManager : NSObject 44 + (BOOL)locationServicesEnabled; 45 @property(assign) id delegate; 46 - (void)startUpdatingLocation; 47 - (void)stopUpdatingLocation; 48 @end 49 50 @interface CLLocation : NSObject<NSCopying, NSCoding> 51 @property(readonly) CLLocationCoordinate2D coordinate; 52 @property(readonly) CLLocationDistance altitude; 53 @property(readonly) CLLocationAccuracy horizontalAccuracy; 54 @property(readonly) CLLocationAccuracy verticalAccuracy; 55 @property(readonly) CLLocationDirection course; 56 @property(readonly) CLLocationSpeed speed; 57 @end 58 59 @protocol CLLocationManagerDelegate 60 - (void)locationManager:(CLLocationManager*)manager 61 didUpdateToLocation:(CLLocation*)newLocation 62 fromLocation:(CLLocation*)oldLocation; 63 - (void)locationManager:(CLLocationManager*)manager 64 didFailWithError:(NSError*)error; 65 @end 66 67 // This wrapper class receives CLLocation objects from CoreLocation, converts 68 // them to Geoposition objects, and passes them on to the data provider class 69 // Note: This class has some specific threading requirements, inherited from 70 // CLLocationManager. The location manaager's start and stop updating 71 // methods must be called from a thread that has an active run loop (which 72 // seems to only be the UI thread) 73 @interface CoreLocationWrapperMac : NSObject<CLLocationManagerDelegate> 74 { 75 @private 76 NSBundle* bundle_; 77 Class locationManagerClass_; 78 id locationManager_; 79 CoreLocationDataProviderMac* dataProvider_; 80 } 81 82 - (id)initWithDataProvider:(CoreLocationDataProviderMac*)dataProvider; 83 - (void)dealloc; 84 85 // Can be called from any thread since it does not require an NSRunLoop. However 86 // it is not threadsafe to receive concurrent calls until after a first 87 // successful call (to avoid |bundle_| being double initialized) 88 - (BOOL)locationDataAvailable; 89 90 // These should always be called from BrowserThread::UI 91 - (void)startLocation; 92 - (void)stopLocation; 93 94 // These should only be called by CLLocationManager 95 - (void)locationManager:(CLLocationManager*)manager 96 didUpdateToLocation:(CLLocation*)newLocation 97 fromLocation:(CLLocation*)oldLocation; 98 - (void)locationManager:(CLLocationManager*)manager 99 didFailWithError:(NSError*)error; 100 - (BOOL)loadCoreLocationBundle; 101 102 @end 103 104 @implementation CoreLocationWrapperMac 105 106 - (id)initWithDataProvider:(CoreLocationDataProviderMac*)dataProvider { 107 DCHECK(dataProvider); 108 dataProvider_ = dataProvider; 109 self = [super init]; 110 return self; 111 } 112 113 - (void)dealloc { 114 [locationManager_ setDelegate:nil]; 115 [locationManager_ release]; 116 [locationManagerClass_ release]; 117 [bundle_ release]; 118 [super dealloc]; 119 } 120 121 // Load the bundle and check to see if location services are enabled 122 // but don't do anything else 123 - (BOOL)locationDataAvailable { 124 return ([self loadCoreLocationBundle] && 125 [locationManagerClass_ locationServicesEnabled]); 126 } 127 128 - (void)startLocation { 129 if ([self locationDataAvailable]) { 130 if (!locationManager_) { 131 locationManager_ = [[locationManagerClass_ alloc] init]; 132 [locationManager_ setDelegate:self]; 133 } 134 [locationManager_ startUpdatingLocation]; 135 } 136 } 137 138 - (void)stopLocation { 139 [locationManager_ stopUpdatingLocation]; 140 } 141 142 - (void)locationManager:(CLLocationManager*)manager 143 didUpdateToLocation:(CLLocation*)newLocation 144 fromLocation:(CLLocation*)oldLocation { 145 Geoposition position; 146 position.latitude = [newLocation coordinate].latitude; 147 position.longitude = [newLocation coordinate].longitude; 148 position.altitude = [newLocation altitude]; 149 position.accuracy = [newLocation horizontalAccuracy]; 150 position.altitude_accuracy = [newLocation verticalAccuracy]; 151 position.speed = [newLocation speed]; 152 position.heading = [newLocation course]; 153 position.timestamp = base::Time::Now(); 154 position.error_code = Geoposition::ERROR_CODE_NONE; 155 dataProvider_->UpdatePosition(&position); 156 } 157 158 - (void)locationManager:(CLLocationManager*)manager 159 didFailWithError:(NSError*)error { 160 Geoposition position; 161 switch ([error code]) { 162 case kCLErrorLocationUnknown: 163 position.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; 164 break; 165 case kCLErrorDenied: 166 position.error_code = Geoposition::ERROR_CODE_PERMISSION_DENIED; 167 break; 168 default: 169 NOTREACHED() << "Unknown CoreLocation error: " << [error code]; 170 return; 171 } 172 dataProvider_->UpdatePosition(&position); 173 } 174 175 - (BOOL)loadCoreLocationBundle { 176 if (!bundle_) { 177 bundle_ = [[NSBundle alloc] 178 initWithPath:@"/System/Library/Frameworks/CoreLocation.framework"]; 179 if (!bundle_) { 180 DLOG(WARNING) << "Couldn't load CoreLocation Framework"; 181 return NO; 182 } 183 184 locationManagerClass_ = [bundle_ classNamed:@"CLLocationManager"]; 185 } 186 187 return YES; 188 } 189 190 @end 191 192 namespace content { 193 194 CoreLocationDataProviderMac::CoreLocationDataProviderMac() { 195 if (base::MessageLoop::current() != 196 GeolocationProviderImpl::GetInstance()->message_loop()) { 197 NOTREACHED() << "CoreLocation data provider must be created on " 198 "the Geolocation thread."; 199 } 200 provider_ = NULL; 201 wrapper_.reset([[CoreLocationWrapperMac alloc] initWithDataProvider:this]); 202 } 203 204 CoreLocationDataProviderMac::~CoreLocationDataProviderMac() { 205 } 206 207 // Returns true if the CoreLocation wrapper can load the framework and 208 // location services are enabled. The pointer argument will only be accessed 209 // in the origin thread. 210 bool CoreLocationDataProviderMac:: 211 StartUpdating(CoreLocationProviderMac* provider) { 212 DCHECK(provider); 213 DCHECK(!provider_) << "StartUpdating called twice"; 214 if (![wrapper_ locationDataAvailable]) return false; 215 provider_ = provider; 216 BrowserThread::PostTask( 217 BrowserThread::UI, FROM_HERE, 218 base::Bind(&CoreLocationDataProviderMac::StartUpdatingTask, this)); 219 return true; 220 } 221 222 // Clears provider_ so that any leftover messages from CoreLocation get ignored 223 void CoreLocationDataProviderMac::StopUpdating() { 224 provider_ = NULL; 225 BrowserThread::PostTask( 226 BrowserThread::UI, FROM_HERE, 227 base::Bind(&CoreLocationDataProviderMac::StopUpdatingTask, this)); 228 } 229 230 void CoreLocationDataProviderMac::UpdatePosition(Geoposition *position) { 231 GeolocationProviderImpl::GetInstance()->message_loop()->PostTask( 232 FROM_HERE, 233 base::Bind(&CoreLocationDataProviderMac::PositionUpdated, this, 234 *position)); 235 } 236 237 // Runs in BrowserThread::UI 238 void CoreLocationDataProviderMac::StartUpdatingTask() { 239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 240 [wrapper_ startLocation]; 241 } 242 243 // Runs in BrowserThread::UI 244 void CoreLocationDataProviderMac::StopUpdatingTask() { 245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 246 [wrapper_ stopLocation]; 247 } 248 249 void CoreLocationDataProviderMac::PositionUpdated(Geoposition position) { 250 DCHECK(base::MessageLoop::current() == 251 GeolocationProviderImpl::GetInstance()->message_loop()); 252 if (provider_) 253 provider_->SetPosition(&position); 254 } 255 256 } // namespace content 257