Home | History | Annotate | Download | only in geolocation
      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 
     12 namespace content {
     13 namespace {
     14 // The maximum period of time we'll wait for a complete set of device data
     15 // before sending the request.
     16 const int kDataCompleteWaitSeconds = 2;
     17 }  // namespace
     18 
     19 // static
     20 const size_t NetworkLocationProvider::PositionCache::kMaximumSize = 10;
     21 
     22 NetworkLocationProvider::PositionCache::PositionCache() {}
     23 
     24 NetworkLocationProvider::PositionCache::~PositionCache() {}
     25 
     26 bool NetworkLocationProvider::PositionCache::CachePosition(
     27     const WifiData& wifi_data,
     28     const Geoposition& position) {
     29   // Check that we can generate a valid key for the device data.
     30   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 set of cell ID and
     57 // WiFi data. Returns the cached position if available, NULL otherwise.
     58 const Geoposition* NetworkLocationProvider::PositionCache::FindPosition(
     59     const WifiData& wifi_data) {
     60   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 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 WifiData& wifi_data,
     74     string16* key) {
     75   // Currently we use only the WiFi data, and base the key only on
     76   // the MAC addresses.
     77   DCHECK(key);
     78   key->clear();
     79   const size_t kCharsPerMacAddress = 6 * 3 + 1;  // e.g. "11:22:33:44:55:66|"
     80   key->reserve(wifi_data.access_point_data.size() * kCharsPerMacAddress);
     81   const string16 separator(ASCIIToUTF16("|"));
     82   for (WifiData::AccessPointDataSet::const_iterator iter =
     83        wifi_data.access_point_data.begin();
     84        iter != wifi_data.access_point_data.end();
     85        iter++) {
     86     *key += separator;
     87     *key += iter->mac_address;
     88     *key += separator;
     89   }
     90   // If the key is the empty string, return false, as we don't want to cache a
     91   // position for such a set of device data.
     92   return !key->empty();
     93 }
     94 
     95 // NetworkLocationProvider factory function
     96 LocationProviderBase* NewNetworkLocationProvider(
     97     AccessTokenStore* access_token_store,
     98     net::URLRequestContextGetter* context,
     99     const GURL& url,
    100     const string16& access_token) {
    101   return new NetworkLocationProvider(
    102       access_token_store, context, url, access_token);
    103 }
    104 
    105 // NetworkLocationProvider
    106 NetworkLocationProvider::NetworkLocationProvider(
    107     AccessTokenStore* access_token_store,
    108     net::URLRequestContextGetter* url_context_getter,
    109     const GURL& url,
    110     const string16& access_token)
    111     : access_token_store_(access_token_store),
    112       wifi_data_provider_(NULL),
    113       is_wifi_data_complete_(false),
    114       access_token_(access_token),
    115       is_permission_granted_(false),
    116       is_new_data_available_(false),
    117       weak_factory_(this) {
    118   // Create the position cache.
    119   position_cache_.reset(new PositionCache());
    120 
    121   request_.reset(new NetworkLocationRequest(url_context_getter, url, this));
    122 }
    123 
    124 NetworkLocationProvider::~NetworkLocationProvider() {
    125   StopProvider();
    126 }
    127 
    128 // LocationProvider implementation
    129 void NetworkLocationProvider::GetPosition(Geoposition *position) {
    130   DCHECK(position);
    131   *position = position_;
    132 }
    133 
    134 void NetworkLocationProvider::RequestRefresh() {
    135   // TODO(joth): When called via the public (base class) interface, this should
    136   // poke each data provider to get them to expedite their next scan.
    137   // Whilst in the delayed start, only send request if all data is ready.
    138   if (!weak_factory_.HasWeakPtrs() || is_wifi_data_complete_) {
    139     RequestPosition();
    140   }
    141 }
    142 
    143 void NetworkLocationProvider::OnPermissionGranted() {
    144   const bool was_permission_granted = is_permission_granted_;
    145   is_permission_granted_ = true;
    146   if (!was_permission_granted && IsStarted()) {
    147     RequestRefresh();
    148   }
    149 }
    150 
    151 // DeviceDataProviderInterface::ListenerInterface implementation.
    152 void NetworkLocationProvider::DeviceDataUpdateAvailable(
    153     WifiDataProvider* provider) {
    154   DCHECK(provider == wifi_data_provider_);
    155   is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_);
    156   OnDeviceDataUpdated();
    157 }
    158 
    159 // NetworkLocationRequest::ListenerInterface implementation.
    160 void NetworkLocationProvider::LocationResponseAvailable(
    161     const Geoposition& position,
    162     bool server_error,
    163     const string16& access_token,
    164     const WifiData& wifi_data) {
    165   DCHECK(CalledOnValidThread());
    166   // Record the position and update our cache.
    167   position_ = position;
    168   if (position.Validate()) {
    169     position_cache_->CachePosition(wifi_data, position);
    170   }
    171 
    172   // Record access_token if it's set.
    173   if (!access_token.empty() && access_token_ != access_token) {
    174     access_token_ = access_token;
    175     access_token_store_->SaveAccessToken(request_->url(), access_token);
    176   }
    177 
    178   // Let listeners know that we now have a position available.
    179   NotifyCallback(position_);
    180 }
    181 
    182 bool NetworkLocationProvider::StartProvider(bool high_accuracy) {
    183   DCHECK(CalledOnValidThread());
    184   if (IsStarted())
    185     return true;
    186   DCHECK(wifi_data_provider_ == NULL);
    187   if (!request_->url().is_valid()) {
    188     LOG(WARNING) << "StartProvider() : Failed, Bad URL: "
    189                  << request_->url().possibly_invalid_spec();
    190     return false;
    191   }
    192 
    193   // Get the device data providers. The first call to Register will create the
    194   // provider and it will be deleted by ref counting.
    195   wifi_data_provider_ = WifiDataProvider::Register(this);
    196 
    197   base::MessageLoop::current()->PostDelayedTask(
    198       FROM_HERE,
    199       base::Bind(&NetworkLocationProvider::RequestPosition,
    200                  weak_factory_.GetWeakPtr()),
    201       base::TimeDelta::FromSeconds(kDataCompleteWaitSeconds));
    202   // Get the device data.
    203   is_wifi_data_complete_ = wifi_data_provider_->GetData(&wifi_data_);
    204   if (is_wifi_data_complete_)
    205     OnDeviceDataUpdated();
    206   return true;
    207 }
    208 
    209 void NetworkLocationProvider::StopProvider() {
    210   DCHECK(CalledOnValidThread());
    211   if (IsStarted()) {
    212     wifi_data_provider_->Unregister(this);
    213   }
    214   wifi_data_provider_ = NULL;
    215   weak_factory_.InvalidateWeakPtrs();
    216 }
    217 
    218 // Other methods
    219 void NetworkLocationProvider::RequestPosition() {
    220   DCHECK(CalledOnValidThread());
    221   if (!is_new_data_available_)
    222     return;
    223 
    224   const Geoposition* cached_position =
    225       position_cache_->FindPosition(wifi_data_);
    226   DCHECK(!device_data_updated_timestamp_.is_null()) <<
    227       "Timestamp must be set before looking up position";
    228   if (cached_position) {
    229     DCHECK(cached_position->Validate());
    230     // Record the position and update its timestamp.
    231     position_ = *cached_position;
    232     // The timestamp of a position fix is determined by the timestamp
    233     // of the source data update. (The value of position_.timestamp from
    234     // the cache could be from weeks ago!)
    235     position_.timestamp = device_data_updated_timestamp_;
    236     is_new_data_available_ = false;
    237     // Let listeners know that we now have a position available.
    238     NotifyCallback(position_);
    239     return;
    240   }
    241   // Don't send network requests until authorized. http://crbug.com/39171
    242   if (!is_permission_granted_)
    243     return;
    244 
    245   weak_factory_.InvalidateWeakPtrs();
    246   is_new_data_available_ = false;
    247 
    248   // TODO(joth): Rather than cancel pending requests, we should create a new
    249   // NetworkLocationRequest for each and hold a set of pending requests.
    250   if (request_->is_request_pending()) {
    251     DVLOG(1) << "NetworkLocationProvider - pre-empting pending network request "
    252                 "with new data. Wifi APs: "
    253              << wifi_data_.access_point_data.size();
    254   }
    255   request_->MakeRequest(access_token_, wifi_data_,
    256                         device_data_updated_timestamp_);
    257 }
    258 
    259 void NetworkLocationProvider::OnDeviceDataUpdated() {
    260   DCHECK(CalledOnValidThread());
    261   device_data_updated_timestamp_ = base::Time::Now();
    262 
    263   is_new_data_available_ = is_wifi_data_complete_;
    264   RequestRefresh();
    265 }
    266 
    267 bool NetworkLocationProvider::IsStarted() const {
    268   return wifi_data_provider_ != NULL;
    269 }
    270 
    271 }  // namespace content
    272