| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/time.h" | |
| 8 #include "base/utf_string_conversions.h" | |
| 9 #include "content/browser/geolocation/access_token_store.h" | |
| 10 | |
| 11 namespace { | |
| 12 // The maximum period of time we'll wait for a complete set of device data | |
| 13 // before sending the request. | |
| 14 const int kDataCompleteWaitPeriod = 1000 * 2; // 2 seconds | |
| 15 } // namespace | |
| 16 | |
| 17 // static | |
| 18 const size_t NetworkLocationProvider::PositionCache::kMaximumSize = 10; | |
| 19 | |
| 20 NetworkLocationProvider::PositionCache::PositionCache() {} | |
| 21 | |
| 22 NetworkLocationProvider::PositionCache::~PositionCache() {} | |
| 23 | |
| 24 bool NetworkLocationProvider::PositionCache::CachePosition( | |
| 25 const GatewayData& gateway_data, | |
| 26 const WifiData& wifi_data, | |
| 27 const Geoposition& position) { | |
| 28 // Check that we can generate a valid key for the device data. | |
| 29 string16 key; | |
| 30 if (!MakeKey(gateway_data, wifi_data, &key)) { | |
| 31 return false; | |
| 32 } | |
| 33 // If the cache is full, remove the oldest entry. | |
| 34 if (cache_.size() == kMaximumSize) { | |
| 35 DCHECK(cache_age_list_.size() == kMaximumSize); | |
| 36 CacheAgeList::iterator oldest_entry = cache_age_list_.begin(); | |
| 37 DCHECK(oldest_entry != cache_age_list_.end()); | |
| 38 cache_.erase(*oldest_entry); | |
| 39 cache_age_list_.erase(oldest_entry); | |
| 40 } | |
| 41 DCHECK_LT(cache_.size(), kMaximumSize); | |
| 42 // Insert the position into the cache. | |
| 43 std::pair<CacheMap::iterator, bool> result = | |
| 44 cache_.insert(std::make_pair(key, position)); | |
| 45 if (!result.second) { | |
| 46 NOTREACHED(); // We never try to add the same key twice. | |
| 47 CHECK_EQ(cache_.size(), cache_age_list_.size()); | |
| 48 return false; | |
| 49 } | |
| 50 cache_age_list_.push_back(result.first); | |
| 51 DCHECK_EQ(cache_.size(), cache_age_list_.size()); | |
| 52 return true; | |
| 53 } | |
| 54 | |
| 55 // Searches for a cached position response for the current set of cell ID and | |
| 56 // WiFi data. Returns the cached position if available, NULL otherwise. | |
| 57 const Geoposition* NetworkLocationProvider::PositionCache::FindPosition( | |
| 58 const GatewayData& gateway_data, | |
| 59 const WifiData& wifi_data) { | |
| 60 string16 key; | |
| 61 if (!MakeKey(gateway_data, 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 a set of | |
| 69 // device data. Returns true if a good key was generated, false otherwise. | |
| 70 // | |
| 71 // static | |
| 72 bool NetworkLocationProvider::PositionCache::MakeKey( | |
| 73 const GatewayData& gateway_data, | |
| 74 const WifiData& wifi_data, | |
| 75 string16* key) { | |
| 76 // Currently we use only the WiFi data and gateway data, and base the | |
| 77 // key only on the MAC addresses. | |
| 78 // TODO(joth): Make use of radio_data. | |
| 79 DCHECK(key); | |
| 80 key->clear(); | |
| 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 + gateway_data.router_data.size() * kCharsPerMacAddress); | |
| 84 const string16 separator(ASCIIToUTF16("|")); | |
| 85 for (GatewayData::RouterDataSet::const_iterator iter = | |
| 86 gateway_data.router_data.begin(); | |
| 87 iter != gateway_data.router_data.end(); | |
| 88 iter++) { | |
| 89 *key += separator; | |
| 90 *key += iter->mac_address; | |
| 91 *key += separator; | |
| 92 } | |
| 93 for (WifiData::AccessPointDataSet::const_iterator iter = | |
| 94 wifi_data.access_point_data.begin(); | |
| 95 iter != wifi_data.access_point_data.end(); | |
| 96 iter++) { | |
| 97 *key += separator; | |
| 98 *key += iter->mac_address; | |
| 99 *key += separator; | |
| 100 } | |
| 101 // If the key is the empty string, return false, as we don't want to cache a | |
| 102 // position for such a set of device data. | |
| 103 return !key->empty(); | |
| 104 } | |
| 105 | |
| 106 // NetworkLocationProvider factory function | |
| 107 LocationProviderBase* NewNetworkLocationProvider( | |
| 108 AccessTokenStore* access_token_store, | |
| 109 URLRequestContextGetter* context, | |
| 110 const GURL& url, | |
| 111 const string16& access_token) { | |
| 112 return new NetworkLocationProvider( | |
| 113 access_token_store, context, url, access_token); | |
| 114 } | |
| 115 | |
| 116 // NetworkLocationProvider | |
| 117 NetworkLocationProvider::NetworkLocationProvider( | |
| 118 AccessTokenStore* access_token_store, | |
| 119 URLRequestContextGetter* url_context_getter, | |
| 120 const GURL& url, | |
| 121 const string16& access_token) | |
| 122 : access_token_store_(access_token_store), | |
| 123 gateway_data_provider_(NULL), | |
| 124 radio_data_provider_(NULL), | |
| 125 wifi_data_provider_(NULL), | |
| 126 is_gateway_data_complete_(false), | |
| 127 is_radio_data_complete_(false), | |
| 128 is_wifi_data_complete_(false), | |
| 129 access_token_(access_token), | |
| 130 is_new_data_available_(false), | |
| 131 ALLOW_THIS_IN_INITIALIZER_LIST(delayed_start_task_(this)) { | |
| 132 // Create the position cache. | |
| 133 position_cache_.reset(new PositionCache()); | |
| 134 | |
| 135 request_.reset(new NetworkLocationRequest(url_context_getter, url, this)); | |
| 136 } | |
| 137 | |
| 138 NetworkLocationProvider::~NetworkLocationProvider() { | |
| 139 StopProvider(); | |
| 140 } | |
| 141 | |
| 142 // LocationProviderBase implementation | |
| 143 void NetworkLocationProvider::GetPosition(Geoposition *position) { | |
| 144 DCHECK(position); | |
| 145 *position = position_; | |
| 146 } | |
| 147 | |
| 148 void NetworkLocationProvider::UpdatePosition() { | |
| 149 // TODO(joth): When called via the public (base class) interface, this should | |
| 150 // poke each data provider to get them to expedite their next scan. | |
| 151 // Whilst in the delayed start, only send request if all data is ready. | |
| 152 if (delayed_start_task_.empty() || | |
| 153 (is_gateway_data_complete_ && is_radio_data_complete_ && | |
| 154 is_wifi_data_complete_)) { | |
| 155 RequestPosition(); | |
| 156 } | |
| 157 } | |
| 158 | |
| 159 void NetworkLocationProvider::OnPermissionGranted( | |
| 160 const GURL& requesting_frame) { | |
| 161 const bool host_was_empty = most_recent_authorized_host_.empty(); | |
| 162 most_recent_authorized_host_ = requesting_frame.host(); | |
| 163 if (host_was_empty && !most_recent_authorized_host_.empty() | |
| 164 && IsStarted()) { | |
| 165 UpdatePosition(); | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 // DeviceDataProviderInterface::ListenerInterface implementation. | |
| 170 void NetworkLocationProvider::DeviceDataUpdateAvailable( | |
| 171 GatewayDataProvider* provider) { | |
| 172 DCHECK(provider == gateway_data_provider_); | |
| 173 is_gateway_data_complete_ = gateway_data_provider_->GetData(&gateway_data_); | |
| 174 OnDeviceDataUpdated(); | |
| 175 } | |
| 176 | |
| 177 void NetworkLocationProvider::DeviceDataUpdateAvailable( | |
| 178 RadioDataProvider* provider) { | |
| 179 DCHECK(provider == radio_data_provider_); | |
| 180 is_radio_data_complete_ = radio_data_provider_->GetData(&radio_data_); | |
| 181 OnDeviceDataUpdated(); | |
| 182 } | |
| 183 | |
| 184 void NetworkLocationProvider::DeviceDataUpdateAvailable( | |
| 185 WifiDataProvider* provider) { | |
| 186 DCHECK(provider == wifi_data_provider_); | |
| 187 is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_); | |
| 188 OnDeviceDataUpdated(); | |
| 189 } | |
| 190 | |
| 191 // NetworkLocationRequest::ListenerInterface implementation. | |
| 192 void NetworkLocationProvider::LocationResponseAvailable( | |
| 193 const Geoposition& position, | |
| 194 bool server_error, | |
| 195 const string16& access_token, | |
| 196 const GatewayData& gateway_data, | |
| 197 const RadioData& radio_data, | |
| 198 const WifiData& wifi_data) { | |
| 199 DCHECK(CalledOnValidThread()); | |
| 200 // Record the position and update our cache. | |
| 201 position_ = position; | |
| 202 if (position.IsValidFix()) { | |
| 203 position_cache_->CachePosition(gateway_data, wifi_data, position); | |
| 204 } | |
| 205 | |
| 206 // Record access_token if it's set. | |
| 207 if (!access_token.empty() && access_token_ != access_token) { | |
| 208 access_token_ = access_token; | |
| 209 access_token_store_->SaveAccessToken(request_->url(), access_token); | |
| 210 } | |
| 211 | |
| 212 // Let listeners know that we now have a position available. | |
| 213 UpdateListeners(); | |
| 214 } | |
| 215 | |
| 216 bool NetworkLocationProvider::StartProvider(bool high_accuracy) { | |
| 217 DCHECK(CalledOnValidThread()); | |
| 218 if (IsStarted()) | |
| 219 return true; | |
| 220 DCHECK(wifi_data_provider_ == NULL); | |
| 221 if (!request_->url().is_valid()) { | |
| 222 LOG(WARNING) << "StartProvider() : Failed, Bad URL: " | |
| 223 << request_->url().possibly_invalid_spec(); | |
| 224 return false; | |
| 225 } | |
| 226 | |
| 227 // Get the device data providers. The first call to Register will create the | |
| 228 // provider and it will be deleted by ref counting. | |
| 229 gateway_data_provider_ = GatewayDataProvider::Register(this); | |
| 230 radio_data_provider_ = RadioDataProvider::Register(this); | |
| 231 wifi_data_provider_ = WifiDataProvider::Register(this); | |
| 232 | |
| 233 MessageLoop::current()->PostDelayedTask( | |
| 234 FROM_HERE, | |
| 235 delayed_start_task_.NewRunnableMethod( | |
| 236 &NetworkLocationProvider::RequestPosition), | |
| 237 kDataCompleteWaitPeriod); | |
| 238 // Get the device data. | |
| 239 is_gateway_data_complete_ = gateway_data_provider_->GetData(&gateway_data_); | |
| 240 is_radio_data_complete_ = radio_data_provider_->GetData(&radio_data_); | |
| 241 is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_); | |
| 242 if (is_gateway_data_complete_ || is_radio_data_complete_ || | |
| 243 is_wifi_data_complete_) | |
| 244 OnDeviceDataUpdated(); | |
| 245 return true; | |
| 246 } | |
| 247 | |
| 248 void NetworkLocationProvider::StopProvider() { | |
| 249 DCHECK(CalledOnValidThread()); | |
| 250 if (IsStarted()) { | |
| 251 gateway_data_provider_->Unregister(this); | |
| 252 radio_data_provider_->Unregister(this); | |
| 253 wifi_data_provider_->Unregister(this); | |
| 254 } | |
| 255 gateway_data_provider_ = NULL; | |
| 256 radio_data_provider_ = NULL; | |
| 257 wifi_data_provider_ = NULL; | |
| 258 delayed_start_task_.RevokeAll(); | |
| 259 } | |
| 260 | |
| 261 // Other methods | |
| 262 void NetworkLocationProvider::RequestPosition() { | |
| 263 DCHECK(CalledOnValidThread()); | |
| 264 if (!is_new_data_available_) | |
| 265 return; | |
| 266 | |
| 267 const Geoposition* cached_position = | |
| 268 position_cache_->FindPosition(gateway_data_, wifi_data_); | |
| 269 DCHECK(!device_data_updated_timestamp_.is_null()) << | |
| 270 "Timestamp must be set before looking up position"; | |
| 271 if (cached_position) { | |
| 272 DCHECK(cached_position->IsValidFix()); | |
| 273 // Record the position and update its timestamp. | |
| 274 position_ = *cached_position; | |
| 275 // The timestamp of a position fix is determined by the timestamp | |
| 276 // of the source data update. (The value of position_.timestamp from | |
| 277 // the cache could be from weeks ago!) | |
| 278 position_.timestamp = device_data_updated_timestamp_; | |
| 279 is_new_data_available_ = false; | |
| 280 // Let listeners know that we now have a position available. | |
| 281 UpdateListeners(); | |
| 282 return; | |
| 283 } | |
| 284 // Don't send network requests until authorized. http://crbug.com/39171 | |
| 285 if (most_recent_authorized_host_.empty()) | |
| 286 return; | |
| 287 | |
| 288 delayed_start_task_.RevokeAll(); | |
| 289 is_new_data_available_ = false; | |
| 290 | |
| 291 // TODO(joth): Rather than cancel pending requests, we should create a new | |
| 292 // NetworkLocationRequest for each and hold a set of pending requests. | |
| 293 if (request_->is_request_pending()) { | |
| 294 DVLOG(1) << "NetworkLocationProvider - pre-empting pending network request " | |
| 295 "with new data. Wifi APs: " | |
| 296 << wifi_data_.access_point_data.size(); | |
| 297 } | |
| 298 // The hostname sent in the request is just to give a first-order | |
| 299 // approximation of usage. We do not need to guarantee that this network | |
| 300 // request was triggered by an API call from this specific host. | |
| 301 request_->MakeRequest(most_recent_authorized_host_, access_token_, | |
| 302 gateway_data_, radio_data_, wifi_data_, | |
| 303 device_data_updated_timestamp_); | |
| 304 } | |
| 305 | |
| 306 void NetworkLocationProvider::OnDeviceDataUpdated() { | |
| 307 DCHECK(CalledOnValidThread()); | |
| 308 device_data_updated_timestamp_ = base::Time::Now(); | |
| 309 | |
| 310 is_new_data_available_ = is_gateway_data_complete_ || | |
| 311 is_radio_data_complete_ || is_wifi_data_complete_; | |
| 312 UpdatePosition(); | |
| 313 } | |
| 314 | |
| 315 bool NetworkLocationProvider::IsStarted() const { | |
| 316 DCHECK_EQ(!!gateway_data_provider_, !!wifi_data_provider_); | |
| 317 DCHECK_EQ(!!radio_data_provider_, !!wifi_data_provider_); | |
| 318 return wifi_data_provider_ != NULL; | |
| 319 } | |
| OLD | NEW |