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