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