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.
5 #include "chrome/browser/extensions/api/location/location_manager.h"
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/common/extensions/api/location.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/geolocation_provider.h"
17 #include "content/public/browser/notification_details.h"
18 #include "content/public/browser/notification_source.h"
19 #include "content/public/common/geoposition.h"
20 #include "extensions/browser/event_router.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/permissions/permission_set.h"
25 using content::BrowserThread;
27 // TODO(vadimt): Add tests.
28 namespace extensions {
30 namespace location = api::location;
32 namespace updatepolicy {
34 // Base class for all update policies for sending a location.
35 class UpdatePolicy : public base::RefCounted<UpdatePolicy> {
37 explicit UpdatePolicy() {}
39 // True if the caller should send an update based off of this policy.
40 virtual bool ShouldSendUpdate(const content::Geoposition&) const = 0;
42 // Updates any policy state on reporting a position.
43 virtual void OnPositionReported(const content::Geoposition&) = 0;
46 virtual ~UpdatePolicy() {}
49 friend class base::RefCounted<UpdatePolicy>;
50 DISALLOW_COPY_AND_ASSIGN(UpdatePolicy);
53 // A policy that controls sending an update below a distance threshold.
54 class DistanceBasedUpdatePolicy : public UpdatePolicy {
56 explicit DistanceBasedUpdatePolicy(double distance_update_threshold_meters) :
57 distance_update_threshold_meters_(distance_update_threshold_meters)
60 // UpdatePolicy Implementation
61 virtual bool ShouldSendUpdate(const content::Geoposition& position) const
63 return !last_updated_position_.Validate() ||
64 Distance(position.latitude,
66 last_updated_position_.latitude,
67 last_updated_position_.longitude) >
68 distance_update_threshold_meters_;
71 virtual void OnPositionReported(const content::Geoposition& position)
73 last_updated_position_ = position;
77 virtual ~DistanceBasedUpdatePolicy() {}
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;
90 const double latitude1Rad = latitude1 * kDegreesToRadians;
91 const double latitude2Rad = latitude2 * kDegreesToRadians;
92 const double latitudeDistRad = latitude2Rad - latitude1Rad;
93 const double longitudeDistRad = (longitude2 - longitude1) *
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) *
102 const double angularDistance = 2.0 * atan2(sqrt(chordLengthSquared),
103 sqrt(1.0 - chordLengthSquared));
104 return kRadius * angularDistance;
107 const double distance_update_threshold_meters_;
108 content::Geoposition last_updated_position_;
110 DISALLOW_COPY_AND_ASSIGN(DistanceBasedUpdatePolicy);
113 // A policy that controls sending an update above a time threshold.
114 class TimeBasedUpdatePolicy : public UpdatePolicy {
116 explicit TimeBasedUpdatePolicy(double time_between_updates_ms) :
117 time_between_updates_ms_(time_between_updates_ms)
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_;
126 virtual void OnPositionReported(const content::Geoposition&) OVERRIDE {
127 last_update_time_ = base::Time::Now();
131 virtual ~TimeBasedUpdatePolicy() {}
133 base::Time last_update_time_;
134 const double time_between_updates_ms_;
136 DISALLOW_COPY_AND_ASSIGN(TimeBasedUpdatePolicy);
139 } // namespace updatepolicy
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> {
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);
154 // Finishes the necessary setup for this object.
155 // Call this method immediately after taking a strong reference
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
164 const std::string& request_name() const { return request_name_; }
166 // Grants permission for using geolocation.
167 static void GrantPermission();
170 friend class base::DeleteHelper<LocationRequest>;
171 friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
173 virtual ~LocationRequest();
175 void AddObserverOnIOThread();
177 void OnLocationUpdate(const content::Geoposition& position);
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);
183 // Updates the policies on sending a position update.
184 void OnPositionReported(const content::Geoposition& position);
187 const std::string request_name_;
189 // Id of the owner extension.
190 const std::string extension_id_;
192 // Owning location manager.
193 const base::WeakPtr<LocationManager> location_manager_;
195 // Holds Update Policies.
196 typedef std::vector<scoped_refptr<updatepolicy::UpdatePolicy> >
198 UpdatePolicyVector update_policies_;
200 content::GeolocationProvider::LocationUpdateCallback callback_;
202 DISALLOW_COPY_AND_ASSIGN(LocationRequest);
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));
217 if (time_between_updates_ms) {
218 update_policies_.push_back(
219 new updatepolicy::TimeBasedUpdatePolicy(
220 *time_between_updates_ms));
223 if (distance_update_threshold_meters) {
224 update_policies_.push_back(
225 new updatepolicy::DistanceBasedUpdatePolicy(
226 *distance_update_threshold_meters));
230 void LocationRequest::Initialize() {
231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
232 callback_ = base::Bind(&LocationRequest::OnLocationUpdate,
233 base::Unretained(this));
235 BrowserThread::PostTask(
238 base::Bind(&LocationRequest::AddObserverOnIOThread,
242 void LocationRequest::GrantPermission() {
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
244 content::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices();
247 LocationRequest::~LocationRequest() {
248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
249 content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback(
253 void LocationRequest::AddObserverOnIOThread() {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
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(
263 void LocationRequest::OnLocationUpdate(const content::Geoposition& position) {
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
265 if (ShouldSendUpdate(position)) {
266 OnPositionReported(position);
267 BrowserThread::PostTask(
270 base::Bind(&LocationManager::SendLocationUpdate,
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();
283 if (!((*it)->ShouldSendUpdate(position))) {
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();
295 (*it)->OnPositionReported(position);
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_));
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.
315 // Override any old request with the same name.
316 RemoveLocationRequest(extension_id, request_name);
318 LocationRequestPointer location_request =
319 new LocationRequest(AsWeakPtr(),
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));
329 void LocationManager::RemoveLocationRequest(const std::string& extension_id,
330 const std::string& name) {
331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
333 std::pair<LocationRequestMap::iterator, LocationRequestMap::iterator>
334 extension_range = location_requests_.equal_range(extension_id);
336 for (LocationRequestMap::iterator it = extension_range.first;
337 it != extension_range.second;
339 if (it->second->request_name() == name) {
340 location_requests_.erase(it);
346 LocationManager::~LocationManager() {
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));
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));
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));
373 scoped_ptr<base::ListValue> args(new base::ListValue());
374 std::string event_name;
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();
384 args->Append(location.ToValue().release());
385 event_name = location::OnLocationUpdate::kEventName;
387 // Set data for onLocationError event.
388 // TODO(vadimt): Set name.
389 args->AppendString(position.error_message);
390 event_name = location::OnLocationError::kEventName;
393 scoped_ptr<Event> event(new Event(event_name, args.Pass()));
395 ExtensionSystem::Get(profile_)->event_router()->
396 DispatchEventToExtension(extension_id, event.Pass());
399 void LocationManager::Observe(int type,
400 const content::NotificationSource& source,
401 const content::NotificationDetails& details) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
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();
411 if (extension->HasAPIPermission(APIPermission::kLocation)) {
412 BrowserThread::PostTask(
415 base::Bind(&LocationRequest::GrantPermission));
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());
432 static base::LazyInstance<ProfileKeyedAPIFactory<LocationManager> >
433 g_factory = LAZY_INSTANCE_INITIALIZER;
436 ProfileKeyedAPIFactory<LocationManager>* LocationManager::GetFactoryInstance() {
437 return g_factory.Pointer();
441 LocationManager* LocationManager::Get(Profile* profile) {
442 return ProfileKeyedAPIFactory<LocationManager>::GetForProfile(profile);
445 } // namespace extensions