1// Copyright (c) 2012 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 "content/browser/geolocation/geolocation_provider_impl.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/location.h"
11#include "base/logging.h"
12#include "base/memory/singleton.h"
13#include "base/message_loop/message_loop.h"
14#include "content/browser/geolocation/location_arbitrator_impl.h"
15#include "content/public/browser/browser_thread.h"
16
17namespace content {
18
19GeolocationProvider* GeolocationProvider::GetInstance() {
20  return GeolocationProviderImpl::GetInstance();
21}
22
23scoped_ptr<GeolocationProvider::Subscription>
24GeolocationProviderImpl::AddLocationUpdateCallback(
25    const LocationUpdateCallback& callback, bool use_high_accuracy) {
26  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
27  scoped_ptr<GeolocationProvider::Subscription> subscription;
28  if (use_high_accuracy) {
29    subscription = high_accuracy_callbacks_.Add(callback);
30  } else {
31    subscription = low_accuracy_callbacks_.Add(callback);
32  }
33
34  OnClientsChanged();
35  if (position_.Validate() ||
36      position_.error_code != Geoposition::ERROR_CODE_NONE) {
37    callback.Run(position_);
38  }
39
40  return subscription.Pass();
41}
42
43void GeolocationProviderImpl::UserDidOptIntoLocationServices() {
44  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
45  bool was_permission_granted = user_did_opt_into_location_services_;
46  user_did_opt_into_location_services_ = true;
47  if (IsRunning() && !was_permission_granted)
48    InformProvidersPermissionGranted();
49}
50
51void GeolocationProviderImpl::OverrideLocationForTesting(
52    const Geoposition& position) {
53  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
54  ignore_location_updates_ = true;
55  NotifyClients(position);
56}
57
58void GeolocationProviderImpl::OnLocationUpdate(const Geoposition& position) {
59  DCHECK(OnGeolocationThread());
60  // Will be true only in testing.
61  if (ignore_location_updates_)
62    return;
63  BrowserThread::PostTask(BrowserThread::UI,
64                          FROM_HERE,
65                          base::Bind(&GeolocationProviderImpl::NotifyClients,
66                                     base::Unretained(this), position));
67}
68
69GeolocationProviderImpl* GeolocationProviderImpl::GetInstance() {
70  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
71  return Singleton<GeolocationProviderImpl>::get();
72}
73
74GeolocationProviderImpl::GeolocationProviderImpl()
75    : base::Thread("Geolocation"),
76      user_did_opt_into_location_services_(false),
77      ignore_location_updates_(false),
78      arbitrator_(NULL) {
79  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
80  high_accuracy_callbacks_.set_removal_callback(
81      base::Bind(&GeolocationProviderImpl::OnClientsChanged,
82                 base::Unretained(this)));
83  low_accuracy_callbacks_.set_removal_callback(
84      base::Bind(&GeolocationProviderImpl::OnClientsChanged,
85                 base::Unretained(this)));
86}
87
88GeolocationProviderImpl::~GeolocationProviderImpl() {
89  Stop();
90  DCHECK(!arbitrator_);
91}
92
93bool GeolocationProviderImpl::OnGeolocationThread() const {
94  return base::MessageLoop::current() == message_loop();
95}
96
97void GeolocationProviderImpl::OnClientsChanged() {
98  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
99  base::Closure task;
100  if (high_accuracy_callbacks_.empty() && low_accuracy_callbacks_.empty()) {
101    DCHECK(IsRunning());
102    if (!ignore_location_updates_) {
103      // We have no more observers, so we clear the cached geoposition so that
104      // when the next observer is added we will not provide a stale position.
105      position_ = Geoposition();
106    }
107    task = base::Bind(&GeolocationProviderImpl::StopProviders,
108                      base::Unretained(this));
109  } else {
110    if (!IsRunning()) {
111      Start();
112      if (user_did_opt_into_location_services_)
113        InformProvidersPermissionGranted();
114    }
115    // Determine a set of options that satisfies all clients.
116    bool use_high_accuracy = !high_accuracy_callbacks_.empty();
117
118    // Send the current options to the providers as they may have changed.
119    task = base::Bind(&GeolocationProviderImpl::StartProviders,
120                      base::Unretained(this),
121                      use_high_accuracy);
122  }
123
124  message_loop()->PostTask(FROM_HERE, task);
125}
126
127void GeolocationProviderImpl::StopProviders() {
128  DCHECK(OnGeolocationThread());
129  DCHECK(arbitrator_);
130  arbitrator_->StopProviders();
131}
132
133void GeolocationProviderImpl::StartProviders(bool use_high_accuracy) {
134  DCHECK(OnGeolocationThread());
135  DCHECK(arbitrator_);
136  arbitrator_->StartProviders(use_high_accuracy);
137}
138
139void GeolocationProviderImpl::InformProvidersPermissionGranted() {
140  DCHECK(IsRunning());
141  if (!OnGeolocationThread()) {
142    message_loop()->PostTask(
143        FROM_HERE,
144        base::Bind(&GeolocationProviderImpl::InformProvidersPermissionGranted,
145                   base::Unretained(this)));
146    return;
147  }
148  DCHECK(OnGeolocationThread());
149  DCHECK(arbitrator_);
150  arbitrator_->OnPermissionGranted();
151}
152
153void GeolocationProviderImpl::NotifyClients(const Geoposition& position) {
154  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
155  DCHECK(position.Validate() ||
156         position.error_code != Geoposition::ERROR_CODE_NONE);
157  position_ = position;
158  high_accuracy_callbacks_.Notify(position_);
159  low_accuracy_callbacks_.Notify(position_);
160}
161
162void GeolocationProviderImpl::Init() {
163  DCHECK(OnGeolocationThread());
164  DCHECK(!arbitrator_);
165  arbitrator_ = CreateArbitrator();
166}
167
168void GeolocationProviderImpl::CleanUp() {
169  DCHECK(OnGeolocationThread());
170  delete arbitrator_;
171  arbitrator_ = NULL;
172}
173
174LocationArbitrator* GeolocationProviderImpl::CreateArbitrator() {
175  LocationArbitratorImpl::LocationUpdateCallback callback = base::Bind(
176      &GeolocationProviderImpl::OnLocationUpdate, base::Unretained(this));
177  return new LocationArbitratorImpl(callback);
178}
179
180}  // namespace content
181