Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / dial / dial_registry.cc
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     VLOG(1) << "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