Home | History | Annotate | Download | only in geolocation
      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