location_manager.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 "chrome/browser/extensions/event_router.h"
8#include "chrome/browser/extensions/extension_system.h"
9#include "chrome/common/extensions/api/location.h"
10#include "chrome/common/extensions/permissions/permission_set.h"
11#include "content/browser/geolocation/geolocation_provider.h"
12
13// TODO(vadimt): Add tests.
14namespace extensions {
15
16namespace location = api::location;
17
18// Request created by chrome.location.watchLocation() call.
19// Lives in the IO thread, except for the constructor.
20class LocationRequest
21    : public content::GeolocationObserver,
22      public base::RefCountedThreadSafe<LocationRequest,
23                                        BrowserThread::DeleteOnIOThread> {
24 public:
25  LocationRequest(
26      const base::WeakPtr<LocationManager>& location_manager,
27      const std::string& extension_id,
28      const std::string& request_name);
29
30  const std::string& request_name() const { return request_name_; }
31
32  // Grants permission for using geolocation.
33  static void GrantPermission();
34
35 private:
36  friend class base::DeleteHelper<LocationRequest>;
37  friend struct content::BrowserThread::DeleteOnThread<
38      content::BrowserThread::IO>;
39
40  virtual ~LocationRequest();
41
42  void AddObserverOnIOThread();
43
44  // GeolocationObserver
45  virtual void OnLocationUpdate(const content::Geoposition& position) OVERRIDE;
46
47  // Request name.
48  const std::string request_name_;
49
50  // Id of the owner extension.
51  const std::string extension_id_;
52
53  // Owning location manager.
54  const base::WeakPtr<LocationManager> location_manager_;
55
56  DISALLOW_COPY_AND_ASSIGN(LocationRequest);
57};
58
59LocationRequest::LocationRequest(
60    const base::WeakPtr<LocationManager>& location_manager,
61    const std::string& extension_id,
62    const std::string& request_name)
63    : request_name_(request_name),
64      extension_id_(extension_id),
65      location_manager_(location_manager) {
66  // TODO(vadimt): use request_info.
67  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
68
69  BrowserThread::PostTask(
70      BrowserThread::IO,
71      FROM_HERE,
72      base::Bind(&LocationRequest::AddObserverOnIOThread,
73                 this));
74}
75
76void LocationRequest::GrantPermission() {
77  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
78  content::GeolocationProvider::GetInstance()->OnPermissionGranted();
79}
80
81LocationRequest::~LocationRequest() {
82  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
83  content::GeolocationProvider::GetInstance()->RemoveObserver(this);
84}
85
86void LocationRequest::AddObserverOnIOThread() {
87  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
88
89  content::GeolocationObserverOptions options(true);
90  // TODO(vadimt): This can get a location cached by GeolocationProvider,
91  // contrary to the API definition which says that creating a location watch
92  // will get new location.
93  content::GeolocationProvider::GetInstance()->AddObserver(this, options);
94}
95
96void LocationRequest::OnLocationUpdate(const content::Geoposition& position) {
97  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
98
99  BrowserThread::PostTask(
100      BrowserThread::UI,
101      FROM_HERE,
102      base::Bind(&LocationManager::SendLocationUpdate,
103                 location_manager_,
104                 extension_id_,
105                 request_name_,
106                 position));
107}
108
109LocationManager::LocationManager(Profile* profile)
110    : profile_(profile) {
111  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
112
113  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
114                 content::Source<Profile>(profile_));
115  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
116                 content::Source<Profile>(profile_));
117}
118
119void LocationManager::AddLocationRequest(const std::string& extension_id,
120                                         const std::string& request_name) {
121  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
122  // TODO(vadimt): Consider resuming requests after restarting the browser.
123
124  // Override any old request with the same name.
125  LocationRequestIterator old_location_request =
126      GetLocationRequestIterator(extension_id, request_name);
127  if (old_location_request.first != location_requests_.end())
128    RemoveLocationRequestIterator(old_location_request);
129
130  LocationRequestPointer location_request = new LocationRequest(AsWeakPtr(),
131                                                                extension_id,
132                                                                request_name);
133  location_requests_[extension_id].push_back(location_request);
134}
135
136void LocationManager::RemoveLocationRequest(const std::string& extension_id,
137                                            const std::string& name) {
138  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
139
140  LocationRequestIterator it = GetLocationRequestIterator(extension_id, name);
141  if (it.first == location_requests_.end())
142    return;
143
144  RemoveLocationRequestIterator(it);
145}
146
147LocationManager::~LocationManager() {
148  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
149}
150
151LocationManager::LocationRequestIterator
152    LocationManager::GetLocationRequestIterator(
153        const std::string& extension_id,
154        const std::string& name) {
155  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
156
157  LocationRequestMap::iterator list = location_requests_.find(extension_id);
158  if (list == location_requests_.end())
159    return make_pair(location_requests_.end(), LocationRequestList::iterator());
160
161  for (LocationRequestList::iterator it = list->second.begin();
162       it != list->second.end(); ++it) {
163    if ((*it)->request_name() == name)
164      return make_pair(list, it);
165  }
166
167  return make_pair(location_requests_.end(), LocationRequestList::iterator());
168}
169
170void LocationManager::GeopositionToApiCoordinates(
171      const content::Geoposition& position,
172      location::Coordinates* coordinates) {
173  coordinates->latitude = position.latitude;
174  coordinates->longitude = position.longitude;
175  if (position.altitude > -10000.)
176    coordinates->altitude.reset(new double(position.altitude));
177  coordinates->accuracy = position.accuracy;
178  if (position.altitude_accuracy >= 0.) {
179    coordinates->altitude_accuracy.reset(
180        new double(position.altitude_accuracy));
181  }
182  if (position.heading >= 0. && position.heading <= 360.)
183    coordinates->heading.reset(new double(position.heading));
184  if (position.speed >= 0.)
185    coordinates->speed.reset(new double(position.speed));
186}
187
188void LocationManager::RemoveLocationRequestIterator(
189    const LocationRequestIterator& iter) {
190  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
191
192  LocationRequestList& list = iter.first->second;
193  list.erase(iter.second);
194  if (list.empty())
195    location_requests_.erase(iter.first);
196}
197
198void LocationManager::SendLocationUpdate(
199    const std::string& extension_id,
200    const std::string& request_name,
201    const content::Geoposition& position) {
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203
204  scoped_ptr<ListValue> args(new ListValue());
205  std::string event_name;
206
207  if (position.Validate() &&
208      position.error_code == content::Geoposition::ERROR_CODE_NONE) {
209    // Set data for onLocationUpdate event.
210    location::Location location;
211    location.name = request_name;
212    GeopositionToApiCoordinates(position, &location.coords);
213    location.timestamp = position.timestamp.ToJsTime();
214
215    args->Append(location.ToValue().release());
216    event_name = "location.onLocationUpdate";
217  } else {
218    // Set data for onLocationError event.
219    // TODO(vadimt): Set name.
220    args->AppendString(position.error_message);
221    event_name = "location.onLocationError";
222  }
223
224  scoped_ptr<Event> event(new Event(event_name, args.Pass()));
225
226  ExtensionSystem::Get(profile_)->event_router()->
227      DispatchEventToExtension(extension_id, event.Pass());
228}
229
230void LocationManager::Observe(int type,
231                              const content::NotificationSource& source,
232                              const content::NotificationDetails& details) {
233  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234
235  switch (type) {
236    case chrome::NOTIFICATION_EXTENSION_LOADED: {
237      // Grants permission to use geolocation once an extension with "location"
238      // permission is loaded.
239      const Extension* extension =
240          content::Details<const Extension>(details).ptr();
241
242      if (extension->HasAPIPermission(APIPermission::kLocation)) {
243          BrowserThread::PostTask(
244              BrowserThread::IO,
245              FROM_HERE,
246              base::Bind(&LocationRequest::GrantPermission));
247      }
248      break;
249    }
250    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
251      // Delete all requests from the unloaded extension.
252      const Extension* extension =
253          content::Details<const UnloadedExtensionInfo>(details)->extension;
254      location_requests_.erase(extension->id());
255      break;
256    }
257    default:
258      NOTREACHED();
259      break;
260  }
261}
262
263}  // namespace extensions
264