Home | History | Annotate | Download | only in dial
      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 "chrome/browser/extensions/api/dial/dial_registry.h"
      6 
      7 #include "base/memory/scoped_ptr.h"
      8 #include "base/stl_util.h"
      9 #include "base/strings/string_number_conversions.h"
     10 #include "base/time/time.h"
     11 #include "base/values.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/extensions/api/dial/dial_api.h"
     14 #include "chrome/browser/extensions/api/dial/dial_device_data.h"
     15 #include "chrome/browser/extensions/api/dial/dial_service.h"
     16 #include "chrome/browser/extensions/event_names.h"
     17 #include "chrome/browser/net/chrome_net_log.h"
     18 #include "chrome/common/extensions/api/dial.h"
     19 
     20 using base::Time;
     21 using base::TimeDelta;
     22 using net::NetworkChangeNotifier;
     23 
     24 namespace extensions {
     25 
     26 DialRegistry::DialRegistry(Observer* dial_api,
     27                            const base::TimeDelta& refresh_interval,
     28                            const base::TimeDelta& expiration,
     29                            const size_t max_devices)
     30   : num_listeners_(0),
     31     discovery_generation_(0),
     32     registry_generation_(0),
     33     last_event_discovery_generation_(0),
     34     last_event_registry_generation_(0),
     35     label_count_(0),
     36     refresh_interval_delta_(refresh_interval),
     37     expiration_delta_(expiration),
     38     max_devices_(max_devices),
     39     dial_api_(dial_api) {
     40   DCHECK(max_devices_ > 0);
     41   NetworkChangeNotifier::AddNetworkChangeObserver(this);
     42 }
     43 
     44 DialRegistry::~DialRegistry() {
     45   DCHECK(thread_checker_.CalledOnValidThread());
     46   DCHECK_EQ(0, num_listeners_);
     47   NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
     48 }
     49 
     50 DialService* DialRegistry::CreateDialService() {
     51   DCHECK(g_browser_process->net_log());
     52   return new DialServiceImpl(g_browser_process->net_log());
     53 }
     54 
     55 void DialRegistry::ClearDialService() {
     56   dial_.reset();
     57 }
     58 
     59 base::Time DialRegistry::Now() const {
     60   return Time::Now();
     61 }
     62 
     63 void DialRegistry::OnListenerAdded() {
     64   DCHECK(thread_checker_.CalledOnValidThread());
     65   if (++num_listeners_ == 1) {
     66     DVLOG(1) << "Listener added; starting periodic discovery.";
     67     StartPeriodicDiscovery();
     68   }
     69 }
     70 
     71 void DialRegistry::OnListenerRemoved() {
     72   DCHECK(thread_checker_.CalledOnValidThread());
     73   DCHECK(num_listeners_ > 0);
     74   if (--num_listeners_ == 0) {
     75     DVLOG(1) << "Listeners removed; stopping periodic discovery.";
     76     StopPeriodicDiscovery();
     77   }
     78 }
     79 
     80 bool DialRegistry::ReadyToDiscover() {
     81   if (num_listeners_ == 0) {
     82     dial_api_->OnDialError(DIAL_NO_LISTENERS);
     83     return false;
     84   }
     85   if (NetworkChangeNotifier::IsOffline()) {
     86     dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
     87     return false;
     88   }
     89   if (NetworkChangeNotifier::IsConnectionCellular(
     90           NetworkChangeNotifier::GetConnectionType())) {
     91     dial_api_->OnDialError(DIAL_CELLULAR_NETWORK);
     92     return false;
     93   }
     94   return true;
     95 }
     96 
     97 bool DialRegistry::DiscoverNow() {
     98   DCHECK(thread_checker_.CalledOnValidThread());
     99   if (!ReadyToDiscover()) {
    100     return false;
    101   }
    102   if (!dial_.get()) {
    103     dial_api_->OnDialError(DIAL_UNKNOWN);
    104     return false;
    105   }
    106 
    107   if (!dial_->HasObserver(this))
    108     NOTREACHED() << "DiscoverNow() called without observing dial_";
    109   discovery_generation_++;
    110   return dial_->Discover();
    111 }
    112 
    113 void DialRegistry::StartPeriodicDiscovery() {
    114   DCHECK(thread_checker_.CalledOnValidThread());
    115   if (!ReadyToDiscover() || dial_.get())
    116     return;
    117 
    118   dial_.reset(CreateDialService());
    119   dial_->AddObserver(this);
    120   DoDiscovery();
    121   repeating_timer_.Start(FROM_HERE,
    122                          refresh_interval_delta_,
    123                          this,
    124                          &DialRegistry::DoDiscovery);
    125 }
    126 
    127 void DialRegistry::DoDiscovery() {
    128   DCHECK(thread_checker_.CalledOnValidThread());
    129   DCHECK(dial_.get());
    130   discovery_generation_++;
    131   DVLOG(1) << "About to discover! Generation = " << discovery_generation_;
    132   dial_->Discover();
    133 }
    134 
    135 void DialRegistry::StopPeriodicDiscovery() {
    136   DCHECK(thread_checker_.CalledOnValidThread());
    137   if (!dial_.get())
    138     return;
    139 
    140   repeating_timer_.Stop();
    141   dial_->RemoveObserver(this);
    142   ClearDialService();
    143 }
    144 
    145 bool DialRegistry::PruneExpiredDevices() {
    146   DCHECK(thread_checker_.CalledOnValidThread());
    147   bool pruned_device = false;
    148   DeviceByLabelMap::iterator i = device_by_label_map_.begin();
    149   while (i != device_by_label_map_.end()) {
    150     linked_ptr<DialDeviceData> device = i->second;
    151     if (IsDeviceExpired(*device)) {
    152       DVLOG(1) << "Device " << device->label() << " expired, removing";
    153       const size_t num_erased_by_id =
    154           device_by_id_map_.erase(device->device_id());
    155       DCHECK_EQ(num_erased_by_id, 1u);
    156       device_by_label_map_.erase(i++);
    157       pruned_device = true;
    158     } else {
    159       ++i;
    160     }
    161   }
    162   return pruned_device;
    163 }
    164 
    165 bool DialRegistry::IsDeviceExpired(const DialDeviceData& device) const {
    166   Time now = Now();
    167 
    168   // Check against our default expiration timeout.
    169   Time default_expiration_time = device.response_time() + expiration_delta_;
    170   if (now > default_expiration_time)
    171     return true;
    172 
    173   // Check against the device's cache-control header, if set.
    174   if (device.has_max_age()) {
    175     Time max_age_expiration_time =
    176       device.response_time() + TimeDelta::FromSeconds(device.max_age());
    177     if (now > max_age_expiration_time)
    178       return true;
    179   }
    180   return false;
    181 }
    182 
    183 void DialRegistry::Clear() {
    184   DCHECK(thread_checker_.CalledOnValidThread());
    185   device_by_id_map_.clear();
    186   device_by_label_map_.clear();
    187   registry_generation_++;
    188 }
    189 
    190 void DialRegistry::MaybeSendEvent() {
    191   DCHECK(thread_checker_.CalledOnValidThread());
    192 
    193   // We need to send an event if:
    194   // (1) We haven't sent one yet in this round of discovery, or
    195   // (2) The device list changed since the last MaybeSendEvent.
    196   bool needs_event =
    197       (last_event_discovery_generation_ < discovery_generation_ ||
    198        last_event_registry_generation_ < registry_generation_);
    199   DVLOG(1) << "ledg = " << last_event_discovery_generation_ << ", dg = "
    200            << discovery_generation_
    201            << ", lerg = " << last_event_registry_generation_ << ", rg = "
    202            << registry_generation_
    203            << ", needs_event = " << needs_event;
    204   if (!needs_event)
    205     return;
    206 
    207   DeviceList device_list;
    208   for (DeviceByLabelMap::const_iterator i = device_by_label_map_.begin();
    209        i != device_by_label_map_.end(); i++) {
    210     device_list.push_back(*(i->second));
    211   }
    212   dial_api_->OnDialDeviceEvent(device_list);
    213 
    214   // Reset watermarks.
    215   last_event_discovery_generation_ = discovery_generation_;
    216   last_event_registry_generation_ = registry_generation_;
    217 }
    218 
    219 std::string DialRegistry::NextLabel() {
    220   DCHECK(thread_checker_.CalledOnValidThread());
    221   return base::IntToString(++label_count_);
    222 }
    223 
    224 void DialRegistry::OnDiscoveryRequest(DialService* service) {
    225   DCHECK(thread_checker_.CalledOnValidThread());
    226   MaybeSendEvent();
    227 }
    228 
    229 void DialRegistry::OnDeviceDiscovered(DialService* service,
    230                                       const DialDeviceData& device) {
    231   DCHECK(thread_checker_.CalledOnValidThread());
    232 
    233   // Adds |device| to our list of devices or updates an existing device, unless
    234   // |device| is a duplicate. Returns true if the list was modified and
    235   // increments the list generation.
    236   linked_ptr<DialDeviceData> device_data(new DialDeviceData(device));
    237   DCHECK(!device_data->device_id().empty());
    238   DCHECK(device_data->label().empty());
    239 
    240   bool did_modify_list = false;
    241   DeviceByIdMap::iterator lookup_result =
    242       device_by_id_map_.find(device_data->device_id());
    243 
    244   if (lookup_result != device_by_id_map_.end()) {
    245     DVLOG(1) << "Found device " << device_data->device_id() << ", merging";
    246 
    247     // Already have previous response.  Merge in data from this response and
    248     // track if there were any API visible changes.
    249     did_modify_list = lookup_result->second->UpdateFrom(*device_data);
    250   } else {
    251     did_modify_list = MaybeAddDevice(device_data);
    252   }
    253 
    254   if (did_modify_list)
    255     registry_generation_++;
    256 
    257   DVLOG(1) << "did_modify_list = " << did_modify_list
    258            << ", generation = " << registry_generation_;
    259 }
    260 
    261 bool DialRegistry::MaybeAddDevice(
    262     const linked_ptr<DialDeviceData>& device_data) {
    263   DCHECK(thread_checker_.CalledOnValidThread());
    264   if (device_by_id_map_.size() == max_devices_) {
    265     DLOG(WARNING) << "Maximum registry size reached.  Cannot add device.";
    266     return false;
    267   }
    268   device_data->set_label(NextLabel());
    269   device_by_id_map_[device_data->device_id()] = device_data;
    270   device_by_label_map_[device_data->label()] = device_data;
    271   DVLOG(1) << "Added device, id = " << device_data->device_id()
    272            << ", label = " << device_data->label();
    273   return true;
    274 }
    275 
    276 void DialRegistry::OnDiscoveryFinished(DialService* service) {
    277   DCHECK(thread_checker_.CalledOnValidThread());
    278   if (PruneExpiredDevices())
    279     registry_generation_++;
    280   MaybeSendEvent();
    281 }
    282 
    283 void DialRegistry::OnError(DialService* service,
    284                            const DialService::DialServiceErrorCode& code) {
    285   DCHECK(thread_checker_.CalledOnValidThread());
    286   switch (code) {
    287     case DialService::DIAL_SERVICE_SOCKET_ERROR:
    288       dial_api_->OnDialError(DIAL_SOCKET_ERROR);
    289       break;
    290     case DialService::DIAL_SERVICE_NO_INTERFACES:
    291       dial_api_->OnDialError(DIAL_NO_INTERFACES);
    292       break;
    293     default:
    294       NOTREACHED();
    295       dial_api_->OnDialError(DIAL_UNKNOWN);
    296       break;
    297   }
    298 }
    299 
    300 void DialRegistry::OnNetworkChanged(
    301     NetworkChangeNotifier::ConnectionType type) {
    302   switch (type) {
    303     case NetworkChangeNotifier::CONNECTION_NONE:
    304       if (dial_.get()) {
    305         DVLOG(1) << "Lost connection, shutting down discovery and clearing"
    306                  << " list.";
    307         dial_api_->OnDialError(DIAL_NETWORK_DISCONNECTED);
    308 
    309         StopPeriodicDiscovery();
    310         // TODO(justinlin): As an optimization, we can probably keep our device
    311         // list around and restore it if we reconnected to the exact same
    312         // network.
    313         Clear();
    314         MaybeSendEvent();
    315       }
    316       break;
    317     case NetworkChangeNotifier::CONNECTION_2G:
    318     case NetworkChangeNotifier::CONNECTION_3G:
    319     case NetworkChangeNotifier::CONNECTION_4G:
    320     case NetworkChangeNotifier::CONNECTION_ETHERNET:
    321     case NetworkChangeNotifier::CONNECTION_WIFI:
    322     case NetworkChangeNotifier::CONNECTION_UNKNOWN:
    323       if (!dial_.get()) {
    324         DVLOG(1) << "Connection detected, restarting discovery.";
    325         StartPeriodicDiscovery();
    326       }
    327       break;
    328   }
    329 }
    330 
    331 }  // namespace extensions
    332