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.
5 #include "content/browser/geolocation/network_location_provider.h"
8 #include "base/location.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/time/time.h"
13 #include "content/public/browser/access_token_store.h"
17 // The maximum period of time we'll wait for a complete set of wifi data
18 // before sending the request.
19 const int kDataCompleteWaitSeconds
= 2;
23 const size_t NetworkLocationProvider::PositionCache::kMaximumSize
= 10;
25 NetworkLocationProvider::PositionCache::PositionCache() {}
27 NetworkLocationProvider::PositionCache::~PositionCache() {}
29 bool NetworkLocationProvider::PositionCache::CachePosition(
30 const WifiData
& wifi_data
,
31 const Geoposition
& position
) {
32 // Check that we can generate a valid key for the wifi data.
34 if (!MakeKey(wifi_data
, &key
)) {
37 // If the cache is full, remove the oldest entry.
38 if (cache_
.size() == kMaximumSize
) {
39 DCHECK(cache_age_list_
.size() == kMaximumSize
);
40 CacheAgeList::iterator oldest_entry
= cache_age_list_
.begin();
41 DCHECK(oldest_entry
!= cache_age_list_
.end());
42 cache_
.erase(*oldest_entry
);
43 cache_age_list_
.erase(oldest_entry
);
45 DCHECK_LT(cache_
.size(), kMaximumSize
);
46 // Insert the position into the cache.
47 std::pair
<CacheMap::iterator
, bool> result
=
48 cache_
.insert(std::make_pair(key
, position
));
50 NOTREACHED(); // We never try to add the same key twice.
51 CHECK_EQ(cache_
.size(), cache_age_list_
.size());
54 cache_age_list_
.push_back(result
.first
);
55 DCHECK_EQ(cache_
.size(), cache_age_list_
.size());
59 // Searches for a cached position response for the current WiFi data. Returns
60 // the cached position if available, NULL otherwise.
61 const Geoposition
* NetworkLocationProvider::PositionCache::FindPosition(
62 const WifiData
& wifi_data
) {
64 if (!MakeKey(wifi_data
, &key
)) {
67 CacheMap::const_iterator iter
= cache_
.find(key
);
68 return iter
== cache_
.end() ? NULL
: &iter
->second
;
71 // Makes the key for the map of cached positions, using the available data.
72 // Returns true if a good key was generated, false otherwise.
75 bool NetworkLocationProvider::PositionCache::MakeKey(
76 const WifiData
& wifi_data
,
77 base::string16
* key
) {
78 // Currently we use only WiFi data and base the key only on the MAC addresses.
81 const size_t kCharsPerMacAddress
= 6 * 3 + 1; // e.g. "11:22:33:44:55:66|"
82 key
->reserve(wifi_data
.access_point_data
.size() * kCharsPerMacAddress
);
83 const base::string16
separator(base::ASCIIToUTF16("|"));
84 for (WifiData::AccessPointDataSet::const_iterator iter
=
85 wifi_data
.access_point_data
.begin();
86 iter
!= wifi_data
.access_point_data
.end();
89 *key
+= iter
->mac_address
;
92 // If the key is the empty string, return false, as we don't want to cache a
93 // position for such data.
97 // NetworkLocationProvider factory function
98 LocationProviderBase
* NewNetworkLocationProvider(
99 AccessTokenStore
* access_token_store
,
100 net::URLRequestContextGetter
* context
,
102 const base::string16
& access_token
) {
103 return new NetworkLocationProvider(
104 access_token_store
, context
, url
, access_token
);
107 // NetworkLocationProvider
108 NetworkLocationProvider::NetworkLocationProvider(
109 AccessTokenStore
* access_token_store
,
110 net::URLRequestContextGetter
* url_context_getter
,
112 const base::string16
& access_token
)
113 : access_token_store_(access_token_store
),
114 wifi_data_provider_manager_(NULL
),
115 wifi_data_update_callback_(
116 base::Bind(&NetworkLocationProvider::OnWifiDataUpdate
,
117 base::Unretained(this))),
118 is_wifi_data_complete_(false),
119 access_token_(access_token
),
120 is_permission_granted_(false),
121 is_new_data_available_(false),
122 weak_factory_(this) {
123 // Create the position cache.
124 position_cache_
.reset(new PositionCache());
126 request_
.reset(new NetworkLocationRequest(
129 base::Bind(&NetworkLocationProvider::OnLocationResponse
,
130 base::Unretained(this))));
133 NetworkLocationProvider::~NetworkLocationProvider() {
137 // LocationProvider implementation
138 void NetworkLocationProvider::GetPosition(Geoposition
* position
) {
140 *position
= position_
;
143 void NetworkLocationProvider::RequestRefresh() {
144 // TODO(joth): When called via the public (base class) interface, this should
145 // poke each data provider to get them to expedite their next scan.
146 // Whilst in the delayed start, only send request if all data is ready.
147 if (!weak_factory_
.HasWeakPtrs() || is_wifi_data_complete_
) {
152 void NetworkLocationProvider::OnPermissionGranted() {
153 const bool was_permission_granted
= is_permission_granted_
;
154 is_permission_granted_
= true;
155 if (!was_permission_granted
&& IsStarted()) {
160 void NetworkLocationProvider::OnWifiDataUpdate() {
161 DCHECK(wifi_data_provider_manager_
);
162 is_wifi_data_complete_
= wifi_data_provider_manager_
->GetData(&wifi_data_
);
166 void NetworkLocationProvider::OnLocationResponse(
167 const Geoposition
& position
,
169 const base::string16
& access_token
,
170 const WifiData
& wifi_data
) {
171 DCHECK(CalledOnValidThread());
172 // Record the position and update our cache.
173 position_
= position
;
174 if (position
.Validate()) {
175 position_cache_
->CachePosition(wifi_data
, position
);
178 // Record access_token if it's set.
179 if (!access_token
.empty() && access_token_
!= access_token
) {
180 access_token_
= access_token
;
181 access_token_store_
->SaveAccessToken(request_
->url(), access_token
);
184 // Let listeners know that we now have a position available.
185 NotifyCallback(position_
);
188 bool NetworkLocationProvider::StartProvider(bool high_accuracy
) {
189 DCHECK(CalledOnValidThread());
192 DCHECK(wifi_data_provider_manager_
== NULL
);
193 if (!request_
->url().is_valid()) {
194 LOG(WARNING
) << "StartProvider() : Failed, Bad URL: "
195 << request_
->url().possibly_invalid_spec();
199 // Registers a callback with the data provider. The first call to Register
200 // will create a singleton data provider and it will be deleted when the last
201 // callback is removed with Unregister.
202 wifi_data_provider_manager_
=
203 WifiDataProviderManager::Register(&wifi_data_update_callback_
);
205 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
206 FROM_HERE
, base::Bind(&NetworkLocationProvider::RequestPosition
,
207 weak_factory_
.GetWeakPtr()),
208 base::TimeDelta::FromSeconds(kDataCompleteWaitSeconds
));
209 // Get the wifi data.
210 is_wifi_data_complete_
= wifi_data_provider_manager_
->GetData(&wifi_data_
);
211 if (is_wifi_data_complete_
)
216 void NetworkLocationProvider::OnWifiDataUpdated() {
217 DCHECK(CalledOnValidThread());
218 wifi_data_updated_timestamp_
= base::Time::Now();
220 is_new_data_available_
= is_wifi_data_complete_
;
224 void NetworkLocationProvider::StopProvider() {
225 DCHECK(CalledOnValidThread());
227 wifi_data_provider_manager_
->Unregister(&wifi_data_update_callback_
);
229 wifi_data_provider_manager_
= NULL
;
230 weak_factory_
.InvalidateWeakPtrs();
234 void NetworkLocationProvider::RequestPosition() {
235 DCHECK(CalledOnValidThread());
236 if (!is_new_data_available_
)
239 const Geoposition
* cached_position
=
240 position_cache_
->FindPosition(wifi_data_
);
241 DCHECK(!wifi_data_updated_timestamp_
.is_null()) <<
242 "Timestamp must be set before looking up position";
243 if (cached_position
) {
244 DCHECK(cached_position
->Validate());
245 // Record the position and update its timestamp.
246 position_
= *cached_position
;
247 // The timestamp of a position fix is determined by the timestamp
248 // of the source data update. (The value of position_.timestamp from
249 // the cache could be from weeks ago!)
250 position_
.timestamp
= wifi_data_updated_timestamp_
;
251 is_new_data_available_
= false;
252 // Let listeners know that we now have a position available.
253 NotifyCallback(position_
);
256 // Don't send network requests until authorized. http://crbug.com/39171
257 if (!is_permission_granted_
)
260 weak_factory_
.InvalidateWeakPtrs();
261 is_new_data_available_
= false;
263 // TODO(joth): Rather than cancel pending requests, we should create a new
264 // NetworkLocationRequest for each and hold a set of pending requests.
265 if (request_
->is_request_pending()) {
266 DVLOG(1) << "NetworkLocationProvider - pre-empting pending network request "
267 "with new data. Wifi APs: "
268 << wifi_data_
.access_point_data
.size();
270 request_
->MakeRequest(access_token_
, wifi_data_
,
271 wifi_data_updated_timestamp_
);
274 bool NetworkLocationProvider::IsStarted() const {
275 return wifi_data_provider_manager_
!= NULL
;
278 } // namespace content