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/common/extensions/api/location.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/geolocation_provider.h"
16 #include "content/public/common/geoposition.h"
17 #include "extensions/browser/event_router.h"
18 #include "extensions/browser/extension_registry.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/common/extension.h"
21 #include "extensions/common/permissions/permission_set.h"
22 #include "extensions/common/permissions/permissions_data.h"
23
24 using content::BrowserThread;
25
26 // TODO(vadimt): Add tests.
27 namespace extensions {
28
29 namespace location = api::location;
30
31 namespace updatepolicy {
32
33 // Base class for all update policies for sending a location.
34 class UpdatePolicy : public base::RefCounted<UpdatePolicy> {
35 public:
UpdatePolicy()36 explicit UpdatePolicy() {}
37
38 // True if the caller should send an update based off of this policy.
39 virtual bool ShouldSendUpdate(const content::Geoposition&) const = 0;
40
41 // Updates any policy state on reporting a position.
42 virtual void OnPositionReported(const content::Geoposition&) = 0;
43
44 protected:
~UpdatePolicy()45 virtual ~UpdatePolicy() {}
46
47 private:
48 friend class base::RefCounted<UpdatePolicy>;
49 DISALLOW_COPY_AND_ASSIGN(UpdatePolicy);
50 };
51
52 // A policy that controls sending an update below a distance threshold.
53 class DistanceBasedUpdatePolicy : public UpdatePolicy {
54 public:
DistanceBasedUpdatePolicy(double distance_update_threshold_meters)55 explicit DistanceBasedUpdatePolicy(double distance_update_threshold_meters) :
56 distance_update_threshold_meters_(distance_update_threshold_meters)
57 {}
58
59 // UpdatePolicy Implementation
ShouldSendUpdate(const content::Geoposition & position) const60 virtual bool ShouldSendUpdate(const content::Geoposition& position) const
61 OVERRIDE {
62 return !last_updated_position_.Validate() ||
63 Distance(position.latitude,
64 position.longitude,
65 last_updated_position_.latitude,
66 last_updated_position_.longitude) >
67 distance_update_threshold_meters_;
68 }
69
OnPositionReported(const content::Geoposition & position)70 virtual void OnPositionReported(const content::Geoposition& position)
71 OVERRIDE {
72 last_updated_position_ = position;
73 }
74
75 private:
~DistanceBasedUpdatePolicy()76 virtual ~DistanceBasedUpdatePolicy() {}
77
78 // Calculates the distance between two latitude and longitude points.
Distance(const double latitude1,const double longitude1,const double latitude2,const double longitude2)79 static double Distance(const double latitude1,
80 const double longitude1,
81 const double latitude2,
82 const double longitude2) {
83 // The earth has a radius of about 6371 km.
84 const double kRadius = 6371000;
85 const double kPi = 3.14159265358979323846;
86 const double kDegreesToRadians = kPi / 180.0;
87
88 // Conversions
89 const double latitude1Rad = latitude1 * kDegreesToRadians;
90 const double latitude2Rad = latitude2 * kDegreesToRadians;
91 const double latitudeDistRad = latitude2Rad - latitude1Rad;
92 const double longitudeDistRad = (longitude2 - longitude1) *
93 kDegreesToRadians;
94
95 // The Haversine Formula determines the great circle distance
96 // between two points on a sphere.
97 const double chordLengthSquared = pow(sin(latitudeDistRad / 2.0), 2) +
98 (pow(sin(longitudeDistRad / 2.0), 2) *
99 cos(latitude1Rad) *
100 cos(latitude2Rad));
101 const double angularDistance = 2.0 * atan2(sqrt(chordLengthSquared),
102 sqrt(1.0 - chordLengthSquared));
103 return kRadius * angularDistance;
104 }
105
106 const double distance_update_threshold_meters_;
107 content::Geoposition last_updated_position_;
108
109 DISALLOW_COPY_AND_ASSIGN(DistanceBasedUpdatePolicy);
110 };
111
112 // A policy that controls sending an update above a time threshold.
113 class TimeBasedUpdatePolicy : public UpdatePolicy {
114 public:
TimeBasedUpdatePolicy(double time_between_updates_ms)115 explicit TimeBasedUpdatePolicy(double time_between_updates_ms) :
116 time_between_updates_ms_(time_between_updates_ms)
117 {}
118
119 // UpdatePolicy Implementation
ShouldSendUpdate(const content::Geoposition &) const120 virtual bool ShouldSendUpdate(const content::Geoposition&) const OVERRIDE {
121 return (base::Time::Now() - last_update_time_).InMilliseconds() >
122 time_between_updates_ms_;
123 }
124
OnPositionReported(const content::Geoposition &)125 virtual void OnPositionReported(const content::Geoposition&) OVERRIDE {
126 last_update_time_ = base::Time::Now();
127 }
128
129 private:
~TimeBasedUpdatePolicy()130 virtual ~TimeBasedUpdatePolicy() {}
131
132 base::Time last_update_time_;
133 const double time_between_updates_ms_;
134
135 DISALLOW_COPY_AND_ASSIGN(TimeBasedUpdatePolicy);
136 };
137
138 } // namespace updatepolicy
139
140 // Request created by chrome.location.watchLocation() call.
141 class LocationRequest : public base::RefCounted<LocationRequest> {
142 public:
143 LocationRequest(
144 LocationManager* location_manager,
145 const std::string& extension_id,
146 const std::string& request_name,
147 const double* distance_update_threshold_meters,
148 const double* time_between_updates_ms);
149
request_name() const150 const std::string& request_name() const { return request_name_; }
151
152 private:
153 friend class base::RefCounted<LocationRequest>;
154 ~LocationRequest();
155
156 void OnLocationUpdate(const content::Geoposition& position);
157
158 // Determines if all policies say to send a position update.
159 // If there are no policies, this always says yes.
160 bool ShouldSendUpdate(const content::Geoposition& position);
161
162 // Updates the policies on sending a position update.
163 void OnPositionReported(const content::Geoposition& position);
164
165 // Request name.
166 const std::string request_name_;
167
168 // Id of the owner extension.
169 const std::string extension_id_;
170
171 // Owning location manager.
172 LocationManager* location_manager_;
173
174 // Holds Update Policies.
175 typedef std::vector<scoped_refptr<updatepolicy::UpdatePolicy> >
176 UpdatePolicyVector;
177 UpdatePolicyVector update_policies_;
178
179 scoped_ptr<content::GeolocationProvider::Subscription>
180 geolocation_subscription_;
181
182 DISALLOW_COPY_AND_ASSIGN(LocationRequest);
183 };
184
LocationRequest(LocationManager * location_manager,const std::string & extension_id,const std::string & request_name,const double * distance_update_threshold_meters,const double * time_between_updates_ms)185 LocationRequest::LocationRequest(
186 LocationManager* location_manager,
187 const std::string& extension_id,
188 const std::string& request_name,
189 const double* distance_update_threshold_meters,
190 const double* time_between_updates_ms)
191 : request_name_(request_name),
192 extension_id_(extension_id),
193 location_manager_(location_manager) {
194 // TODO(vadimt): use request_info.
195 if (time_between_updates_ms) {
196 update_policies_.push_back(
197 new updatepolicy::TimeBasedUpdatePolicy(
198 *time_between_updates_ms));
199 }
200
201 if (distance_update_threshold_meters) {
202 update_policies_.push_back(
203 new updatepolicy::DistanceBasedUpdatePolicy(
204 *distance_update_threshold_meters));
205 }
206
207 // TODO(vadimt): This can get a location cached by GeolocationProvider,
208 // contrary to the API definition which says that creating a location watch
209 // will get new location.
210 geolocation_subscription_ = content::GeolocationProvider::GetInstance()->
211 AddLocationUpdateCallback(
212 base::Bind(&LocationRequest::OnLocationUpdate,
213 base::Unretained(this)),
214 true);
215 }
216
~LocationRequest()217 LocationRequest::~LocationRequest() {
218 }
219
OnLocationUpdate(const content::Geoposition & position)220 void LocationRequest::OnLocationUpdate(const content::Geoposition& position) {
221 if (ShouldSendUpdate(position)) {
222 OnPositionReported(position);
223 location_manager_->SendLocationUpdate(
224 extension_id_, request_name_, position);
225 }
226 }
227
ShouldSendUpdate(const content::Geoposition & position)228 bool LocationRequest::ShouldSendUpdate(const content::Geoposition& position) {
229 for (UpdatePolicyVector::iterator it = update_policies_.begin();
230 it != update_policies_.end();
231 ++it) {
232 if (!((*it)->ShouldSendUpdate(position))) {
233 return false;
234 }
235 }
236 return true;
237 }
238
OnPositionReported(const content::Geoposition & position)239 void LocationRequest::OnPositionReported(const content::Geoposition& position) {
240 for (UpdatePolicyVector::iterator it = update_policies_.begin();
241 it != update_policies_.end();
242 ++it) {
243 (*it)->OnPositionReported(position);
244 }
245 }
246
LocationManager(content::BrowserContext * context)247 LocationManager::LocationManager(content::BrowserContext* context)
248 : browser_context_(context), extension_registry_observer_(this) {
249 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
250 }
251
AddLocationRequest(const std::string & extension_id,const std::string & request_name,const double * distance_update_threshold_meters,const double * time_between_updates_ms)252 void LocationManager::AddLocationRequest(
253 const std::string& extension_id,
254 const std::string& request_name,
255 const double* distance_update_threshold_meters,
256 const double* time_between_updates_ms) {
257 DCHECK_CURRENTLY_ON(BrowserThread::UI);
258 // TODO(vadimt): Consider resuming requests after restarting the browser.
259
260 // Override any old request with the same name.
261 RemoveLocationRequest(extension_id, request_name);
262
263 LocationRequestPointer location_request =
264 new LocationRequest(this,
265 extension_id,
266 request_name,
267 distance_update_threshold_meters,
268 time_between_updates_ms);
269 location_requests_.insert(
270 LocationRequestMap::value_type(extension_id, location_request));
271 }
272
RemoveLocationRequest(const std::string & extension_id,const std::string & name)273 void LocationManager::RemoveLocationRequest(const std::string& extension_id,
274 const std::string& name) {
275 DCHECK_CURRENTLY_ON(BrowserThread::UI);
276
277 std::pair<LocationRequestMap::iterator, LocationRequestMap::iterator>
278 extension_range = location_requests_.equal_range(extension_id);
279
280 for (LocationRequestMap::iterator it = extension_range.first;
281 it != extension_range.second;
282 ++it) {
283 if (it->second->request_name() == name) {
284 location_requests_.erase(it);
285 return;
286 }
287 }
288 }
289
~LocationManager()290 LocationManager::~LocationManager() {
291 }
292
GeopositionToApiCoordinates(const content::Geoposition & position,location::Coordinates * coordinates)293 void LocationManager::GeopositionToApiCoordinates(
294 const content::Geoposition& position,
295 location::Coordinates* coordinates) {
296 coordinates->latitude = position.latitude;
297 coordinates->longitude = position.longitude;
298 if (position.altitude > -10000.)
299 coordinates->altitude.reset(new double(position.altitude));
300 coordinates->accuracy = position.accuracy;
301 if (position.altitude_accuracy >= 0.) {
302 coordinates->altitude_accuracy.reset(
303 new double(position.altitude_accuracy));
304 }
305 if (position.heading >= 0. && position.heading <= 360.)
306 coordinates->heading.reset(new double(position.heading));
307 if (position.speed >= 0.)
308 coordinates->speed.reset(new double(position.speed));
309 }
310
SendLocationUpdate(const std::string & extension_id,const std::string & request_name,const content::Geoposition & position)311 void LocationManager::SendLocationUpdate(
312 const std::string& extension_id,
313 const std::string& request_name,
314 const content::Geoposition& position) {
315 DCHECK_CURRENTLY_ON(BrowserThread::UI);
316
317 scoped_ptr<base::ListValue> args(new base::ListValue());
318 std::string event_name;
319
320 if (position.Validate() &&
321 position.error_code == content::Geoposition::ERROR_CODE_NONE) {
322 // Set data for onLocationUpdate event.
323 location::Location location;
324 location.name = request_name;
325 GeopositionToApiCoordinates(position, &location.coords);
326 location.timestamp = position.timestamp.ToJsTime();
327
328 args->Append(location.ToValue().release());
329 event_name = location::OnLocationUpdate::kEventName;
330 } else {
331 // Set data for onLocationError event.
332 // TODO(vadimt): Set name.
333 args->AppendString(position.error_message);
334 event_name = location::OnLocationError::kEventName;
335 }
336
337 scoped_ptr<Event> event(new Event(event_name, args.Pass()));
338
339 EventRouter::Get(browser_context_)
340 ->DispatchEventToExtension(extension_id, event.Pass());
341 }
342
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)343 void LocationManager::OnExtensionLoaded(
344 content::BrowserContext* browser_context,
345 const Extension* extension) {
346 // Grants permission to use geolocation once an extension with "location"
347 // permission is loaded.
348 if (extension->permissions_data()->HasAPIPermission(
349 APIPermission::kLocation)) {
350 content::GeolocationProvider::GetInstance()
351 ->UserDidOptIntoLocationServices();
352 }
353 }
354
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)355 void LocationManager::OnExtensionUnloaded(
356 content::BrowserContext* browser_context,
357 const Extension* extension,
358 UnloadedExtensionInfo::Reason reason) {
359 // Delete all requests from the unloaded extension.
360 location_requests_.erase(extension->id());
361 }
362
363 static base::LazyInstance<BrowserContextKeyedAPIFactory<LocationManager> >
364 g_factory = LAZY_INSTANCE_INITIALIZER;
365
366 // static
367 BrowserContextKeyedAPIFactory<LocationManager>*
GetFactoryInstance()368 LocationManager::GetFactoryInstance() {
369 return g_factory.Pointer();
370 }
371
372 // static
Get(content::BrowserContext * context)373 LocationManager* LocationManager::Get(content::BrowserContext* context) {
374 return BrowserContextKeyedAPIFactory<LocationManager>::Get(context);
375 }
376
377 } // namespace extensions
378