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 "chromeos/network/shill_property_handler.h" 6 7 #include "base/bind.h" 8 #include "base/format_macros.h" 9 #include "base/stl_util.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/stringprintf.h" 12 #include "base/values.h" 13 #include "chromeos/dbus/dbus_thread_manager.h" 14 #include "chromeos/dbus/shill_device_client.h" 15 #include "chromeos/dbus/shill_ipconfig_client.h" 16 #include "chromeos/dbus/shill_manager_client.h" 17 #include "chromeos/dbus/shill_profile_client.h" 18 #include "chromeos/dbus/shill_service_client.h" 19 #include "chromeos/network/network_event_log.h" 20 #include "chromeos/network/network_state.h" 21 #include "dbus/object_path.h" 22 #include "third_party/cros_system_api/dbus/service_constants.h" 23 24 namespace { 25 26 // Limit the number of services or devices we observe. Since they are listed in 27 // priority order, it should be reasonable to ignore services past this. 28 const size_t kMaxObserved = 100; 29 30 const base::ListValue* GetListValue(const std::string& key, 31 const base::Value& value) { 32 const base::ListValue* vlist = NULL; 33 if (!value.GetAsList(&vlist)) { 34 LOG(ERROR) << "Error parsing key as list: " << key; 35 return NULL; 36 } 37 return vlist; 38 } 39 40 } // namespace 41 42 namespace chromeos { 43 namespace internal { 44 45 // Class to manage Shill service property changed observers. Observers are 46 // added on construction and removed on destruction. Runs the handler when 47 // OnPropertyChanged is called. 48 class ShillPropertyObserver : public ShillPropertyChangedObserver { 49 public: 50 typedef base::Callback<void(ManagedState::ManagedType type, 51 const std::string& service, 52 const std::string& name, 53 const base::Value& value)> Handler; 54 55 ShillPropertyObserver(ManagedState::ManagedType type, 56 const std::string& path, 57 const Handler& handler) 58 : type_(type), 59 path_(path), 60 handler_(handler) { 61 if (type_ == ManagedState::MANAGED_TYPE_NETWORK) { 62 DBusThreadManager::Get()->GetShillServiceClient()-> 63 AddPropertyChangedObserver(dbus::ObjectPath(path_), this); 64 } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) { 65 DBusThreadManager::Get()->GetShillDeviceClient()-> 66 AddPropertyChangedObserver(dbus::ObjectPath(path_), this); 67 } else { 68 NOTREACHED(); 69 } 70 } 71 72 virtual ~ShillPropertyObserver() { 73 if (type_ == ManagedState::MANAGED_TYPE_NETWORK) { 74 DBusThreadManager::Get()->GetShillServiceClient()-> 75 RemovePropertyChangedObserver(dbus::ObjectPath(path_), this); 76 } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) { 77 DBusThreadManager::Get()->GetShillDeviceClient()-> 78 RemovePropertyChangedObserver(dbus::ObjectPath(path_), this); 79 } else { 80 NOTREACHED(); 81 } 82 } 83 84 // ShillPropertyChangedObserver overrides. 85 virtual void OnPropertyChanged(const std::string& key, 86 const base::Value& value) OVERRIDE { 87 handler_.Run(type_, path_, key, value); 88 } 89 90 private: 91 ManagedState::ManagedType type_; 92 std::string path_; 93 Handler handler_; 94 95 DISALLOW_COPY_AND_ASSIGN(ShillPropertyObserver); 96 }; 97 98 //------------------------------------------------------------------------------ 99 // ShillPropertyHandler 100 101 ShillPropertyHandler::ShillPropertyHandler(Listener* listener) 102 : listener_(listener), 103 shill_manager_(DBusThreadManager::Get()->GetShillManagerClient()) { 104 } 105 106 ShillPropertyHandler::~ShillPropertyHandler() { 107 // Delete network service observers. 108 STLDeleteContainerPairSecondPointers( 109 observed_networks_.begin(), observed_networks_.end()); 110 STLDeleteContainerPairSecondPointers( 111 observed_devices_.begin(), observed_devices_.end()); 112 CHECK(shill_manager_ == DBusThreadManager::Get()->GetShillManagerClient()); 113 shill_manager_->RemovePropertyChangedObserver(this); 114 } 115 116 void ShillPropertyHandler::Init() { 117 UpdateManagerProperties(); 118 shill_manager_->AddPropertyChangedObserver(this); 119 } 120 121 void ShillPropertyHandler::UpdateManagerProperties() { 122 NET_LOG_EVENT("UpdateManagerProperties", ""); 123 shill_manager_->GetProperties( 124 base::Bind(&ShillPropertyHandler::ManagerPropertiesCallback, 125 AsWeakPtr())); 126 } 127 128 bool ShillPropertyHandler::IsTechnologyAvailable( 129 const std::string& technology) const { 130 return available_technologies_.count(technology) != 0; 131 } 132 133 bool ShillPropertyHandler::IsTechnologyEnabled( 134 const std::string& technology) const { 135 return enabled_technologies_.count(technology) != 0; 136 } 137 138 bool ShillPropertyHandler::IsTechnologyEnabling( 139 const std::string& technology) const { 140 return enabling_technologies_.count(technology) != 0; 141 } 142 143 bool ShillPropertyHandler::IsTechnologyUninitialized( 144 const std::string& technology) const { 145 return uninitialized_technologies_.count(technology) != 0; 146 } 147 148 void ShillPropertyHandler::SetTechnologyEnabled( 149 const std::string& technology, 150 bool enabled, 151 const network_handler::ErrorCallback& error_callback) { 152 if (enabled) { 153 enabling_technologies_.insert(technology); 154 shill_manager_->EnableTechnology( 155 technology, 156 base::Bind(&base::DoNothing), 157 base::Bind(&ShillPropertyHandler::EnableTechnologyFailed, 158 AsWeakPtr(), technology, error_callback)); 159 } else { 160 // Immediately clear locally from enabled and enabling lists. 161 enabled_technologies_.erase(technology); 162 enabling_technologies_.erase(technology); 163 shill_manager_->DisableTechnology( 164 technology, 165 base::Bind(&base::DoNothing), 166 base::Bind(&network_handler::ShillErrorCallbackFunction, 167 "SetTechnologyEnabled Failed", 168 technology, error_callback)); 169 } 170 } 171 172 void ShillPropertyHandler::SetCheckPortalList( 173 const std::string& check_portal_list) { 174 base::StringValue value(check_portal_list); 175 shill_manager_->SetProperty( 176 flimflam::kCheckPortalListProperty, 177 value, 178 base::Bind(&base::DoNothing), 179 base::Bind(&network_handler::ShillErrorCallbackFunction, 180 "SetCheckPortalList Failed", 181 "", network_handler::ErrorCallback())); 182 } 183 184 void ShillPropertyHandler::RequestScan() const { 185 shill_manager_->RequestScan( 186 "", 187 base::Bind(&base::DoNothing), 188 base::Bind(&network_handler::ShillErrorCallbackFunction, 189 "RequestScan Failed", 190 "", network_handler::ErrorCallback())); 191 } 192 193 void ShillPropertyHandler::ConnectToBestServices() const { 194 NET_LOG_EVENT("ConnectToBestServices", ""); 195 shill_manager_->ConnectToBestServices( 196 base::Bind(&base::DoNothing), 197 base::Bind(&network_handler::ShillErrorCallbackFunction, 198 "ConnectToBestServices Failed", 199 "", network_handler::ErrorCallback())); 200 } 201 202 void ShillPropertyHandler::RequestProperties(ManagedState::ManagedType type, 203 const std::string& path) { 204 VLOG(2) << "Request Properties: " << type << " : " << path; 205 if (pending_updates_[type].find(path) != pending_updates_[type].end()) 206 return; // Update already requested. 207 208 pending_updates_[type].insert(path); 209 if (type == ManagedState::MANAGED_TYPE_NETWORK || 210 type == ManagedState::MANAGED_TYPE_FAVORITE) { 211 DBusThreadManager::Get()->GetShillServiceClient()->GetProperties( 212 dbus::ObjectPath(path), 213 base::Bind(&ShillPropertyHandler::GetPropertiesCallback, 214 AsWeakPtr(), type, path)); 215 } else if (type == ManagedState::MANAGED_TYPE_DEVICE) { 216 DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties( 217 dbus::ObjectPath(path), 218 base::Bind(&ShillPropertyHandler::GetPropertiesCallback, 219 AsWeakPtr(), type, path)); 220 } else { 221 NOTREACHED(); 222 } 223 } 224 225 void ShillPropertyHandler::OnPropertyChanged(const std::string& key, 226 const base::Value& value) { 227 if (ManagerPropertyChanged(key, value)) { 228 std::string detail = key; 229 detail += " = " + network_event_log::ValueAsString(value); 230 NET_LOG_DEBUG("ManagerPropertyChanged", detail); 231 listener_->NotifyManagerPropertyChanged(); 232 } 233 CheckPendingStateListUpdates(key); 234 } 235 236 //------------------------------------------------------------------------------ 237 // Private methods 238 239 void ShillPropertyHandler::ManagerPropertiesCallback( 240 DBusMethodCallStatus call_status, 241 const base::DictionaryValue& properties) { 242 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 243 NET_LOG_ERROR("ManagerPropertiesCallback", 244 base::StringPrintf("Failed: %d", call_status)); 245 return; 246 } 247 NET_LOG_EVENT("ManagerPropertiesCallback", "Success"); 248 bool notify = false; 249 const base::Value* update_service_value = NULL; 250 const base::Value* update_service_complete_value = NULL; 251 for (base::DictionaryValue::Iterator iter(properties); 252 !iter.IsAtEnd(); iter.Advance()) { 253 // Defer updating Services until all other properties have been updated. 254 if (iter.key() == flimflam::kServicesProperty) 255 update_service_value = &iter.value(); 256 else if (iter.key() == shill::kServiceCompleteListProperty) 257 update_service_complete_value = &iter.value(); 258 else 259 notify |= ManagerPropertyChanged(iter.key(), iter.value()); 260 } 261 // Update Services which can safely assume other properties have been set. 262 if (update_service_value) { 263 notify |= ManagerPropertyChanged(flimflam::kServicesProperty, 264 *update_service_value); 265 } 266 // Update ServiceCompleteList which skips entries that have already been 267 // requested for Services. 268 if (update_service_complete_value) { 269 notify |= ManagerPropertyChanged(shill::kServiceCompleteListProperty, 270 *update_service_complete_value); 271 } 272 273 if (notify) 274 listener_->NotifyManagerPropertyChanged(); 275 CheckPendingStateListUpdates(""); 276 } 277 278 void ShillPropertyHandler::CheckPendingStateListUpdates( 279 const std::string& key) { 280 // Once there are no pending updates, signal the state list changed callbacks. 281 if ((key.empty() || key == flimflam::kServicesProperty) && 282 pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0) { 283 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_NETWORK); 284 } 285 // Both Network update requests and Favorite update requests will affect 286 // the list of favorites, so wait for both to complete. 287 if ((key.empty() || key == shill::kServiceCompleteListProperty) && 288 pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0 && 289 pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) { 290 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE); 291 } 292 if ((key.empty() || key == flimflam::kDevicesProperty) && 293 pending_updates_[ManagedState::MANAGED_TYPE_DEVICE].size() == 0) { 294 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_DEVICE); 295 } 296 } 297 298 bool ShillPropertyHandler::ManagerPropertyChanged(const std::string& key, 299 const base::Value& value) { 300 bool notify_manager_changed = false; 301 if (key == flimflam::kServicesProperty) { 302 const base::ListValue* vlist = GetListValue(key, value); 303 if (vlist) { 304 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 305 UpdateProperties(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 306 // UpdateObserved used to use kServiceWatchListProperty for TYPE_NETWORK, 307 // however that prevents us from receiving Strength updates from inactive 308 // networks. The overhead for observing all services is not unreasonable 309 // (and we limit the max number of observed services to kMaxObserved). 310 UpdateObserved(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 311 } 312 } else if (key == shill::kServiceCompleteListProperty) { 313 const ListValue* vlist = GetListValue(key, value); 314 if (vlist) { 315 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_FAVORITE, *vlist); 316 UpdateProperties(ManagedState::MANAGED_TYPE_FAVORITE, *vlist); 317 } 318 } else if (key == flimflam::kDevicesProperty) { 319 const base::ListValue* vlist = GetListValue(key, value); 320 if (vlist) { 321 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 322 UpdateProperties(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 323 UpdateObserved(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 324 } 325 } else if (key == flimflam::kAvailableTechnologiesProperty) { 326 const base::ListValue* vlist = GetListValue(key, value); 327 if (vlist) { 328 UpdateAvailableTechnologies(*vlist); 329 notify_manager_changed = true; 330 } 331 } else if (key == flimflam::kEnabledTechnologiesProperty) { 332 const base::ListValue* vlist = GetListValue(key, value); 333 if (vlist) { 334 UpdateEnabledTechnologies(*vlist); 335 notify_manager_changed = true; 336 } 337 } else if (key == shill::kUninitializedTechnologiesProperty) { 338 const base::ListValue* vlist = GetListValue(key, value); 339 if (vlist) { 340 UpdateUninitializedTechnologies(*vlist); 341 notify_manager_changed = true; 342 } 343 } else if (key == flimflam::kProfilesProperty) { 344 listener_->ProfileListChanged(); 345 } else if (key == flimflam::kCheckPortalListProperty) { 346 std::string check_portal_list; 347 if (value.GetAsString(&check_portal_list)) { 348 listener_->CheckPortalListChanged(check_portal_list); 349 notify_manager_changed = true; 350 } 351 } else { 352 VLOG(2) << "Ignored Manager Property: " << key; 353 } 354 return notify_manager_changed; 355 } 356 357 void ShillPropertyHandler::UpdateProperties(ManagedState::ManagedType type, 358 const base::ListValue& entries) { 359 std::set<std::string>& requested_updates = requested_updates_[type]; 360 std::set<std::string>& requested_service_updates = 361 requested_updates_[ManagedState::MANAGED_TYPE_NETWORK]; // For favorites 362 std::set<std::string> new_requested_updates; 363 VLOG(2) << "Update Properties: " << type << " Entries: " << entries.GetSize(); 364 for (base::ListValue::const_iterator iter = entries.begin(); 365 iter != entries.end(); ++iter) { 366 std::string path; 367 (*iter)->GetAsString(&path); 368 if (path.empty()) 369 continue; 370 if (type == ManagedState::MANAGED_TYPE_FAVORITE && 371 requested_service_updates.count(path) > 0) 372 continue; // Update already requested 373 if (requested_updates.find(path) == requested_updates.end()) 374 RequestProperties(type, path); 375 new_requested_updates.insert(path); 376 } 377 requested_updates.swap(new_requested_updates); 378 } 379 380 void ShillPropertyHandler::UpdateObserved(ManagedState::ManagedType type, 381 const base::ListValue& entries) { 382 DCHECK(type == ManagedState::MANAGED_TYPE_NETWORK || 383 type == ManagedState::MANAGED_TYPE_DEVICE); 384 ShillPropertyObserverMap& observer_map = 385 (type == ManagedState::MANAGED_TYPE_NETWORK) 386 ? observed_networks_ : observed_devices_; 387 ShillPropertyObserverMap new_observed; 388 for (base::ListValue::const_iterator iter1 = entries.begin(); 389 iter1 != entries.end(); ++iter1) { 390 std::string path; 391 (*iter1)->GetAsString(&path); 392 if (path.empty()) 393 continue; 394 ShillPropertyObserverMap::iterator iter2 = observer_map.find(path); 395 if (iter2 != observer_map.end()) { 396 new_observed[path] = iter2->second; 397 } else { 398 // Create an observer for future updates. 399 new_observed[path] = new ShillPropertyObserver( 400 type, path, base::Bind( 401 &ShillPropertyHandler::PropertyChangedCallback, AsWeakPtr())); 402 } 403 observer_map.erase(path); 404 // Limit the number of observed services. 405 if (new_observed.size() >= kMaxObserved) 406 break; 407 } 408 // Delete network service observers still in observer_map. 409 for (ShillPropertyObserverMap::iterator iter = observer_map.begin(); 410 iter != observer_map.end(); ++iter) { 411 delete iter->second; 412 } 413 observer_map.swap(new_observed); 414 } 415 416 void ShillPropertyHandler::UpdateAvailableTechnologies( 417 const base::ListValue& technologies) { 418 available_technologies_.clear(); 419 NET_LOG_EVENT("AvailableTechnologiesChanged", 420 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 421 for (base::ListValue::const_iterator iter = technologies.begin(); 422 iter != technologies.end(); ++iter) { 423 std::string technology; 424 (*iter)->GetAsString(&technology); 425 DCHECK(!technology.empty()); 426 available_technologies_.insert(technology); 427 } 428 } 429 430 void ShillPropertyHandler::UpdateEnabledTechnologies( 431 const base::ListValue& technologies) { 432 enabled_technologies_.clear(); 433 NET_LOG_EVENT("EnabledTechnologiesChanged", 434 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 435 for (base::ListValue::const_iterator iter = technologies.begin(); 436 iter != technologies.end(); ++iter) { 437 std::string technology; 438 (*iter)->GetAsString(&technology); 439 DCHECK(!technology.empty()); 440 enabled_technologies_.insert(technology); 441 enabling_technologies_.erase(technology); 442 } 443 } 444 445 void ShillPropertyHandler::UpdateUninitializedTechnologies( 446 const base::ListValue& technologies) { 447 uninitialized_technologies_.clear(); 448 NET_LOG_EVENT("UninitializedTechnologiesChanged", 449 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 450 for (base::ListValue::const_iterator iter = technologies.begin(); 451 iter != technologies.end(); ++iter) { 452 std::string technology; 453 (*iter)->GetAsString(&technology); 454 DCHECK(!technology.empty()); 455 uninitialized_technologies_.insert(technology); 456 } 457 } 458 459 void ShillPropertyHandler::EnableTechnologyFailed( 460 const std::string& technology, 461 const network_handler::ErrorCallback& error_callback, 462 const std::string& dbus_error_name, 463 const std::string& dbus_error_message) { 464 enabling_technologies_.erase(technology); 465 network_handler::ShillErrorCallbackFunction( 466 "EnableTechnology Failed", 467 technology, error_callback, 468 dbus_error_name, dbus_error_message); 469 } 470 471 void ShillPropertyHandler::GetPropertiesCallback( 472 ManagedState::ManagedType type, 473 const std::string& path, 474 DBusMethodCallStatus call_status, 475 const base::DictionaryValue& properties) { 476 VLOG(2) << "GetPropertiesCallback: " << type << " : " << path; 477 pending_updates_[type].erase(path); 478 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 479 NET_LOG_ERROR("Failed to get properties", 480 base::StringPrintf("%s: %d", path.c_str(), call_status)); 481 return; 482 } 483 listener_->UpdateManagedStateProperties(type, path, properties); 484 // Update Favorite properties for networks in the Services list. 485 if (type == ManagedState::MANAGED_TYPE_NETWORK) { 486 // Only networks with a ProfilePath set are Favorites. 487 std::string profile_path; 488 properties.GetStringWithoutPathExpansion( 489 flimflam::kProfileProperty, &profile_path); 490 if (!profile_path.empty()) { 491 listener_->UpdateManagedStateProperties( 492 ManagedState::MANAGED_TYPE_FAVORITE, path, properties); 493 } 494 } 495 // Request IPConfig parameters for networks. 496 if (type == ManagedState::MANAGED_TYPE_NETWORK && 497 properties.HasKey(shill::kIPConfigProperty)) { 498 std::string ip_config_path; 499 if (properties.GetString(shill::kIPConfigProperty, &ip_config_path)) { 500 DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties( 501 dbus::ObjectPath(ip_config_path), 502 base::Bind(&ShillPropertyHandler::GetIPConfigCallback, 503 AsWeakPtr(), path)); 504 } 505 } 506 507 // Notify the listener only when all updates for that type have completed. 508 if (pending_updates_[type].size() == 0) { 509 listener_->ManagedStateListChanged(type); 510 // Notify that Favorites have changed when notifying for Networks if there 511 // are no additional Favorite updates pending. 512 if (type == ManagedState::MANAGED_TYPE_NETWORK && 513 pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) { 514 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE); 515 } 516 } 517 } 518 519 void ShillPropertyHandler::PropertyChangedCallback( 520 ManagedState::ManagedType type, 521 const std::string& path, 522 const std::string& key, 523 const base::Value& value) { 524 if (type == ManagedState::MANAGED_TYPE_NETWORK) 525 NetworkServicePropertyChangedCallback(path, key, value); 526 else if (type == ManagedState::MANAGED_TYPE_DEVICE) 527 NetworkDevicePropertyChangedCallback(path, key, value); 528 else 529 NOTREACHED(); 530 } 531 532 void ShillPropertyHandler::NetworkServicePropertyChangedCallback( 533 const std::string& path, 534 const std::string& key, 535 const base::Value& value) { 536 if (key == shill::kIPConfigProperty) { 537 // Request the IPConfig for the network and update network properties 538 // when the request completes. 539 std::string ip_config_path; 540 value.GetAsString(&ip_config_path); 541 DCHECK(!ip_config_path.empty()); 542 DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties( 543 dbus::ObjectPath(ip_config_path), 544 base::Bind(&ShillPropertyHandler::GetIPConfigCallback, 545 AsWeakPtr(), path)); 546 } else { 547 listener_->UpdateNetworkServiceProperty(path, key, value); 548 } 549 } 550 551 void ShillPropertyHandler::GetIPConfigCallback( 552 const std::string& service_path, 553 DBusMethodCallStatus call_status, 554 const base::DictionaryValue& properties) { 555 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 556 NET_LOG_ERROR("Failed to get IP Config properties", 557 base::StringPrintf("%s: %d", 558 service_path.c_str(), call_status)); 559 return; 560 } 561 UpdateIPConfigProperty(service_path, properties, 562 flimflam::kAddressProperty); 563 UpdateIPConfigProperty(service_path, properties, 564 flimflam::kNameServersProperty); 565 UpdateIPConfigProperty(service_path, properties, 566 flimflam::kPrefixlenProperty); 567 UpdateIPConfigProperty(service_path, properties, 568 flimflam::kGatewayProperty); 569 UpdateIPConfigProperty(service_path, properties, 570 shill::kWebProxyAutoDiscoveryUrlProperty); 571 } 572 573 void ShillPropertyHandler::UpdateIPConfigProperty( 574 const std::string& service_path, 575 const base::DictionaryValue& properties, 576 const char* property) { 577 const base::Value* value; 578 if (!properties.GetWithoutPathExpansion(property, &value)) { 579 LOG(ERROR) << "Failed to get IPConfig property: " << property 580 << ", for: " << service_path; 581 return; 582 } 583 listener_->UpdateNetworkServiceProperty( 584 service_path, NetworkState::IPConfigProperty(property), *value); 585 } 586 587 void ShillPropertyHandler::NetworkDevicePropertyChangedCallback( 588 const std::string& path, 589 const std::string& key, 590 const base::Value& value) { 591 listener_->UpdateDeviceProperty(path, key, value); 592 } 593 594 } // namespace internal 595 } // namespace chromeos 596