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