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