1 // Copyright (c) 2013 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 #include "chrome/browser/extensions/api/location/location_manager.h" 6 7 #include <math.h> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/lazy_instance.h" 12 #include "base/time/time.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/extensions/event_router.h" 15 #include "chrome/browser/extensions/extension_system.h" 16 #include "chrome/common/extensions/api/location.h" 17 #include "chrome/common/extensions/extension.h" 18 #include "chrome/common/extensions/permissions/permission_set.h" 19 #include "content/public/browser/browser_thread.h" 20 #include "content/public/browser/geolocation_provider.h" 21 #include "content/public/browser/notification_details.h" 22 #include "content/public/browser/notification_source.h" 23 #include "content/public/common/geoposition.h" 24 25 using content::BrowserThread; 26 27 // TODO(vadimt): Add tests. 28 namespace extensions { 29 30 namespace location = api::location; 31 32 namespace updatepolicy { 33 34 // Base class for all update policies for sending a location. 35 class UpdatePolicy : public base::RefCounted<UpdatePolicy> { 36 public: 37 explicit UpdatePolicy() {} 38 39 // True if the caller should send an update based off of this policy. 40 virtual bool ShouldSendUpdate(const content::Geoposition&) const = 0; 41 42 // Updates any policy state on reporting a position. 43 virtual void OnPositionReported(const content::Geoposition&) = 0; 44 45 protected: 46 virtual ~UpdatePolicy() {} 47 48 private: 49 friend class base::RefCounted<UpdatePolicy>; 50 DISALLOW_COPY_AND_ASSIGN(UpdatePolicy); 51 }; 52 53 // A policy that controls sending an update below a distance threshold. 54 class DistanceBasedUpdatePolicy : public UpdatePolicy { 55 public: 56 explicit DistanceBasedUpdatePolicy(double distance_update_threshold_meters) : 57 distance_update_threshold_meters_(distance_update_threshold_meters) 58 {} 59 60 // UpdatePolicy Implementation 61 virtual bool ShouldSendUpdate(const content::Geoposition& position) const 62 OVERRIDE { 63 return !last_updated_position_.Validate() || 64 Distance(position.latitude, 65 position.longitude, 66 last_updated_position_.latitude, 67 last_updated_position_.longitude) > 68 distance_update_threshold_meters_; 69 } 70 71 virtual void OnPositionReported(const content::Geoposition& position) 72 OVERRIDE { 73 last_updated_position_ = position; 74 } 75 76 private: 77 virtual ~DistanceBasedUpdatePolicy() {} 78 79 // Calculates the distance between two latitude and longitude points. 80 static double Distance(const double latitude1, 81 const double longitude1, 82 const double latitude2, 83 const double longitude2) { 84 // The earth has a radius of about 6371 km. 85 const double kRadius = 6371000; 86 const double kPi = 3.14159265358979323846; 87 const double kDegreesToRadians = kPi / 180.0; 88 89 // Conversions 90 const double latitude1Rad = latitude1 * kDegreesToRadians; 91 const double latitude2Rad = latitude2 * kDegreesToRadians; 92 const double latitudeDistRad = latitude2Rad - latitude1Rad; 93 const double longitudeDistRad = (longitude2 - longitude1) * 94 kDegreesToRadians; 95 96 // The Haversine Formula determines the great circle distance 97 // between two points on a sphere. 98 const double chordLengthSquared = pow(sin(latitudeDistRad / 2.0), 2) + 99 (pow(sin(longitudeDistRad / 2.0), 2) * 100 cos(latitude1Rad) * 101 cos(latitude2Rad)); 102 const double angularDistance = 2.0 * atan2(sqrt(chordLengthSquared), 103 sqrt(1.0 - chordLengthSquared)); 104 return kRadius * angularDistance; 105 } 106 107 const double distance_update_threshold_meters_; 108 content::Geoposition last_updated_position_; 109 110 DISALLOW_COPY_AND_ASSIGN(DistanceBasedUpdatePolicy); 111 }; 112 113 // A policy that controls sending an update above a time threshold. 114 class TimeBasedUpdatePolicy : public UpdatePolicy { 115 public: 116 explicit TimeBasedUpdatePolicy(double time_between_updates_ms) : 117 time_between_updates_ms_(time_between_updates_ms) 118 {} 119 120 // UpdatePolicy Implementation 121 virtual bool ShouldSendUpdate(const content::Geoposition&) const OVERRIDE { 122 return (base::Time::Now() - last_update_time_).InMilliseconds() > 123 time_between_updates_ms_; 124 } 125 126 virtual void OnPositionReported(const content::Geoposition&) OVERRIDE { 127 last_update_time_ = base::Time::Now(); 128 } 129 130 private: 131 virtual ~TimeBasedUpdatePolicy() {} 132 133 base::Time last_update_time_; 134 const double time_between_updates_ms_; 135 136 DISALLOW_COPY_AND_ASSIGN(TimeBasedUpdatePolicy); 137 }; 138 139 } // namespace updatepolicy 140 141 // Request created by chrome.location.watchLocation() call. 142 // Lives in the IO thread, except for the constructor. 143 class LocationRequest 144 : public base::RefCountedThreadSafe<LocationRequest, 145 BrowserThread::DeleteOnIOThread> { 146 public: 147 LocationRequest( 148 const base::WeakPtr<LocationManager>& location_manager, 149 const std::string& extension_id, 150 const std::string& request_name, 151 const double* distance_update_threshold_meters, 152 const double* time_between_updates_ms); 153 154 // Finishes the necessary setup for this object. 155 // Call this method immediately after taking a strong reference 156 // to this object. 157 // 158 // Ideally, we would do this at construction time, but currently 159 // our refcount starts at zero. BrowserThread::PostTask will take a ref 160 // and potentially release it before we are done, destroying us in the 161 // constructor. 162 void Initialize(); 163 164 const std::string& request_name() const { return request_name_; } 165 166 // Grants permission for using geolocation. 167 static void GrantPermission(); 168 169 private: 170 friend class base::DeleteHelper<LocationRequest>; 171 friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>; 172 173 virtual ~LocationRequest(); 174 175 void AddObserverOnIOThread(); 176 177 void OnLocationUpdate(const content::Geoposition& position); 178 179 // Determines if all policies say to send a position update. 180 // If there are no policies, this always says yes. 181 bool ShouldSendUpdate(const content::Geoposition& position); 182 183 // Updates the policies on sending a position update. 184 void OnPositionReported(const content::Geoposition& position); 185 186 // Request name. 187 const std::string request_name_; 188 189 // Id of the owner extension. 190 const std::string extension_id_; 191 192 // Owning location manager. 193 const base::WeakPtr<LocationManager> location_manager_; 194 195 // Holds Update Policies. 196 typedef std::vector<scoped_refptr<updatepolicy::UpdatePolicy> > 197 UpdatePolicyVector; 198 UpdatePolicyVector update_policies_; 199 200 content::GeolocationProvider::LocationUpdateCallback callback_; 201 202 DISALLOW_COPY_AND_ASSIGN(LocationRequest); 203 }; 204 205 LocationRequest::LocationRequest( 206 const base::WeakPtr<LocationManager>& location_manager, 207 const std::string& extension_id, 208 const std::string& request_name, 209 const double* distance_update_threshold_meters, 210 const double* time_between_updates_ms) 211 : request_name_(request_name), 212 extension_id_(extension_id), 213 location_manager_(location_manager) { 214 // TODO(vadimt): use request_info. 215 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 216 217 if (time_between_updates_ms) { 218 update_policies_.push_back( 219 new updatepolicy::TimeBasedUpdatePolicy( 220 *time_between_updates_ms)); 221 } 222 223 if (distance_update_threshold_meters) { 224 update_policies_.push_back( 225 new updatepolicy::DistanceBasedUpdatePolicy( 226 *distance_update_threshold_meters)); 227 } 228 } 229 230 void LocationRequest::Initialize() { 231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 232 callback_ = base::Bind(&LocationRequest::OnLocationUpdate, 233 base::Unretained(this)); 234 235 BrowserThread::PostTask( 236 BrowserThread::IO, 237 FROM_HERE, 238 base::Bind(&LocationRequest::AddObserverOnIOThread, 239 this)); 240 } 241 242 void LocationRequest::GrantPermission() { 243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 244 content::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); 245 } 246 247 LocationRequest::~LocationRequest() { 248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 249 content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback( 250 callback_); 251 } 252 253 void LocationRequest::AddObserverOnIOThread() { 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 255 256 // TODO(vadimt): This can get a location cached by GeolocationProvider, 257 // contrary to the API definition which says that creating a location watch 258 // will get new location. 259 content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback( 260 callback_, true); 261 } 262 263 void LocationRequest::OnLocationUpdate(const content::Geoposition& position) { 264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 265 if (ShouldSendUpdate(position)) { 266 OnPositionReported(position); 267 BrowserThread::PostTask( 268 BrowserThread::UI, 269 FROM_HERE, 270 base::Bind(&LocationManager::SendLocationUpdate, 271 location_manager_, 272 extension_id_, 273 request_name_, 274 position)); 275 } 276 } 277 278 bool LocationRequest::ShouldSendUpdate(const content::Geoposition& position) { 279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 280 for (UpdatePolicyVector::iterator it = update_policies_.begin(); 281 it != update_policies_.end(); 282 ++it) { 283 if (!((*it)->ShouldSendUpdate(position))) { 284 return false; 285 } 286 } 287 return true; 288 } 289 290 void LocationRequest::OnPositionReported(const content::Geoposition& position) { 291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 292 for (UpdatePolicyVector::iterator it = update_policies_.begin(); 293 it != update_policies_.end(); 294 ++it) { 295 (*it)->OnPositionReported(position); 296 } 297 } 298 299 LocationManager::LocationManager(Profile* profile) 300 : profile_(profile) { 301 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 302 content::Source<Profile>(profile_)); 303 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 304 content::Source<Profile>(profile_)); 305 } 306 307 void LocationManager::AddLocationRequest( 308 const std::string& extension_id, 309 const std::string& request_name, 310 const double* distance_update_threshold_meters, 311 const double* time_between_updates_ms) { 312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 313 // TODO(vadimt): Consider resuming requests after restarting the browser. 314 315 // Override any old request with the same name. 316 RemoveLocationRequest(extension_id, request_name); 317 318 LocationRequestPointer location_request = 319 new LocationRequest(AsWeakPtr(), 320 extension_id, 321 request_name, 322 distance_update_threshold_meters, 323 time_between_updates_ms); 324 location_request->Initialize(); 325 location_requests_.insert( 326 LocationRequestMap::value_type(extension_id, location_request)); 327 } 328 329 void LocationManager::RemoveLocationRequest(const std::string& extension_id, 330 const std::string& name) { 331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 332 333 std::pair<LocationRequestMap::iterator, LocationRequestMap::iterator> 334 extension_range = location_requests_.equal_range(extension_id); 335 336 for (LocationRequestMap::iterator it = extension_range.first; 337 it != extension_range.second; 338 ++it) { 339 if (it->second->request_name() == name) { 340 location_requests_.erase(it); 341 return; 342 } 343 } 344 } 345 346 LocationManager::~LocationManager() { 347 } 348 349 void LocationManager::GeopositionToApiCoordinates( 350 const content::Geoposition& position, 351 location::Coordinates* coordinates) { 352 coordinates->latitude = position.latitude; 353 coordinates->longitude = position.longitude; 354 if (position.altitude > -10000.) 355 coordinates->altitude.reset(new double(position.altitude)); 356 coordinates->accuracy = position.accuracy; 357 if (position.altitude_accuracy >= 0.) { 358 coordinates->altitude_accuracy.reset( 359 new double(position.altitude_accuracy)); 360 } 361 if (position.heading >= 0. && position.heading <= 360.) 362 coordinates->heading.reset(new double(position.heading)); 363 if (position.speed >= 0.) 364 coordinates->speed.reset(new double(position.speed)); 365 } 366 367 void LocationManager::SendLocationUpdate( 368 const std::string& extension_id, 369 const std::string& request_name, 370 const content::Geoposition& position) { 371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 372 373 scoped_ptr<base::ListValue> args(new base::ListValue()); 374 std::string event_name; 375 376 if (position.Validate() && 377 position.error_code == content::Geoposition::ERROR_CODE_NONE) { 378 // Set data for onLocationUpdate event. 379 location::Location location; 380 location.name = request_name; 381 GeopositionToApiCoordinates(position, &location.coords); 382 location.timestamp = position.timestamp.ToJsTime(); 383 384 args->Append(location.ToValue().release()); 385 event_name = "location.onLocationUpdate"; 386 } else { 387 // Set data for onLocationError event. 388 // TODO(vadimt): Set name. 389 args->AppendString(position.error_message); 390 event_name = "location.onLocationError"; 391 } 392 393 scoped_ptr<Event> event(new Event(event_name, args.Pass())); 394 395 ExtensionSystem::Get(profile_)->event_router()-> 396 DispatchEventToExtension(extension_id, event.Pass()); 397 } 398 399 void LocationManager::Observe(int type, 400 const content::NotificationSource& source, 401 const content::NotificationDetails& details) { 402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 403 404 switch (type) { 405 case chrome::NOTIFICATION_EXTENSION_LOADED: { 406 // Grants permission to use geolocation once an extension with "location" 407 // permission is loaded. 408 const Extension* extension = 409 content::Details<const Extension>(details).ptr(); 410 411 if (extension->HasAPIPermission(APIPermission::kLocation)) { 412 BrowserThread::PostTask( 413 BrowserThread::IO, 414 FROM_HERE, 415 base::Bind(&LocationRequest::GrantPermission)); 416 } 417 break; 418 } 419 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 420 // Delete all requests from the unloaded extension. 421 const Extension* extension = 422 content::Details<const UnloadedExtensionInfo>(details)->extension; 423 location_requests_.erase(extension->id()); 424 break; 425 } 426 default: 427 NOTREACHED(); 428 break; 429 } 430 } 431 432 static base::LazyInstance<ProfileKeyedAPIFactory<LocationManager> > 433 g_factory = LAZY_INSTANCE_INITIALIZER; 434 435 // static 436 ProfileKeyedAPIFactory<LocationManager>* LocationManager::GetFactoryInstance() { 437 return &g_factory.Get(); 438 } 439 440 // static 441 LocationManager* LocationManager::Get(Profile* profile) { 442 return ProfileKeyedAPIFactory<LocationManager>::GetForProfile(profile); 443 } 444 445 } // namespace extensions 446