shill_property_handler.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 24namespace { 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. 28const size_t kMaxObserved = 100; 29 30const 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 42namespace chromeos { 43namespace 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. 48class 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 101ShillPropertyHandler::ShillPropertyHandler(Listener* listener) 102 : listener_(listener), 103 shill_manager_(DBusThreadManager::Get()->GetShillManagerClient()) { 104} 105 106ShillPropertyHandler::~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 116void ShillPropertyHandler::Init() { 117 UpdateManagerProperties(); 118 shill_manager_->AddPropertyChangedObserver(this); 119} 120 121void ShillPropertyHandler::UpdateManagerProperties() { 122 NET_LOG_EVENT("UpdateManagerProperties", ""); 123 shill_manager_->GetProperties( 124 base::Bind(&ShillPropertyHandler::ManagerPropertiesCallback, 125 AsWeakPtr())); 126} 127 128bool ShillPropertyHandler::IsTechnologyAvailable( 129 const std::string& technology) const { 130 return available_technologies_.count(technology) != 0; 131} 132 133bool ShillPropertyHandler::IsTechnologyEnabled( 134 const std::string& technology) const { 135 return enabled_technologies_.count(technology) != 0; 136} 137 138bool ShillPropertyHandler::IsTechnologyEnabling( 139 const std::string& technology) const { 140 return enabling_technologies_.count(technology) != 0; 141} 142 143bool ShillPropertyHandler::IsTechnologyUninitialized( 144 const std::string& technology) const { 145 return uninitialized_technologies_.count(technology) != 0; 146} 147 148void 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 172void ShillPropertyHandler::SetCheckPortalList( 173 const std::string& check_portal_list) { 174 base::StringValue value(check_portal_list); 175 shill_manager_->SetProperty( 176 shill::kCheckPortalListProperty, 177 value, 178 base::Bind(&base::DoNothing), 179 base::Bind(&network_handler::ShillErrorCallbackFunction, 180 "SetCheckPortalList Failed", 181 "", network_handler::ErrorCallback())); 182} 183 184void 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 193void 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 202void 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 225void ShillPropertyHandler::OnPropertyChanged(const std::string& key, 226 const base::Value& value) { 227 ManagerPropertyChanged(key, value); 228 CheckPendingStateListUpdates(key); 229} 230 231//------------------------------------------------------------------------------ 232// Private methods 233 234void ShillPropertyHandler::ManagerPropertiesCallback( 235 DBusMethodCallStatus call_status, 236 const base::DictionaryValue& properties) { 237 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 238 NET_LOG_ERROR("ManagerPropertiesCallback", 239 base::StringPrintf("Failed: %d", call_status)); 240 return; 241 } 242 NET_LOG_EVENT("ManagerPropertiesCallback", "Success"); 243 const base::Value* update_service_value = NULL; 244 const base::Value* update_service_complete_value = NULL; 245 for (base::DictionaryValue::Iterator iter(properties); 246 !iter.IsAtEnd(); iter.Advance()) { 247 // Defer updating Services until all other properties have been updated. 248 if (iter.key() == shill::kServicesProperty) 249 update_service_value = &iter.value(); 250 else if (iter.key() == shill::kServiceCompleteListProperty) 251 update_service_complete_value = &iter.value(); 252 else 253 ManagerPropertyChanged(iter.key(), iter.value()); 254 } 255 // Update Services which can safely assume other properties have been set. 256 if (update_service_value) 257 ManagerPropertyChanged(shill::kServicesProperty, *update_service_value); 258 // Update ServiceCompleteList which skips entries that have already been 259 // requested for Services. 260 if (update_service_complete_value) { 261 ManagerPropertyChanged(shill::kServiceCompleteListProperty, 262 *update_service_complete_value); 263 } 264 265 CheckPendingStateListUpdates(""); 266} 267 268void ShillPropertyHandler::CheckPendingStateListUpdates( 269 const std::string& key) { 270 // Once there are no pending updates, signal the state list changed callbacks. 271 if ((key.empty() || key == shill::kServicesProperty) && 272 pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0) { 273 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_NETWORK); 274 } 275 // Both Network update requests and Favorite update requests will affect 276 // the list of favorites, so wait for both to complete. 277 if ((key.empty() || key == shill::kServiceCompleteListProperty) && 278 pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0 && 279 pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) { 280 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE); 281 } 282 if ((key.empty() || key == shill::kDevicesProperty) && 283 pending_updates_[ManagedState::MANAGED_TYPE_DEVICE].size() == 0) { 284 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_DEVICE); 285 } 286} 287 288void ShillPropertyHandler::ManagerPropertyChanged(const std::string& key, 289 const base::Value& value) { 290 if (key == shill::kDefaultServiceProperty) { 291 std::string service_path; 292 value.GetAsString(&service_path); 293 listener_->DefaultNetworkServiceChanged(service_path); 294 } else if (key == shill::kServicesProperty) { 295 const base::ListValue* vlist = GetListValue(key, value); 296 if (vlist) { 297 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 298 UpdateProperties(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 299 // UpdateObserved used to use kServiceWatchListProperty for TYPE_NETWORK, 300 // however that prevents us from receiving Strength updates from inactive 301 // networks. The overhead for observing all services is not unreasonable 302 // (and we limit the max number of observed services to kMaxObserved). 303 UpdateObserved(ManagedState::MANAGED_TYPE_NETWORK, *vlist); 304 } 305 } else if (key == shill::kServiceCompleteListProperty) { 306 const base::ListValue* vlist = GetListValue(key, value); 307 if (vlist) { 308 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_FAVORITE, *vlist); 309 UpdateProperties(ManagedState::MANAGED_TYPE_FAVORITE, *vlist); 310 } 311 } else if (key == shill::kDevicesProperty) { 312 const base::ListValue* vlist = GetListValue(key, value); 313 if (vlist) { 314 listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 315 UpdateProperties(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 316 UpdateObserved(ManagedState::MANAGED_TYPE_DEVICE, *vlist); 317 } 318 } else if (key == shill::kAvailableTechnologiesProperty) { 319 const base::ListValue* vlist = GetListValue(key, value); 320 if (vlist) 321 UpdateAvailableTechnologies(*vlist); 322 } else if (key == shill::kEnabledTechnologiesProperty) { 323 const base::ListValue* vlist = GetListValue(key, value); 324 if (vlist) 325 UpdateEnabledTechnologies(*vlist); 326 } else if (key == shill::kUninitializedTechnologiesProperty) { 327 const base::ListValue* vlist = GetListValue(key, value); 328 if (vlist) 329 UpdateUninitializedTechnologies(*vlist); 330 } else if (key == shill::kProfilesProperty) { 331 listener_->ProfileListChanged(); 332 } else if (key == shill::kCheckPortalListProperty) { 333 std::string check_portal_list; 334 if (value.GetAsString(&check_portal_list)) 335 listener_->CheckPortalListChanged(check_portal_list); 336 } else { 337 VLOG(2) << "Ignored Manager Property: " << key; 338 } 339} 340 341void ShillPropertyHandler::UpdateProperties(ManagedState::ManagedType type, 342 const base::ListValue& entries) { 343 std::set<std::string>& requested_updates = requested_updates_[type]; 344 std::set<std::string>& requested_service_updates = 345 requested_updates_[ManagedState::MANAGED_TYPE_NETWORK]; // For favorites 346 std::set<std::string> new_requested_updates; 347 VLOG(2) << "Update Properties: " << type << " Entries: " << entries.GetSize(); 348 for (base::ListValue::const_iterator iter = entries.begin(); 349 iter != entries.end(); ++iter) { 350 std::string path; 351 (*iter)->GetAsString(&path); 352 if (path.empty()) 353 continue; 354 if (type == ManagedState::MANAGED_TYPE_FAVORITE && 355 requested_service_updates.count(path) > 0) 356 continue; // Update already requested 357 358 // We add a special case for devices here to work around an issue in shill 359 // that prevents it from sending property changed signals for cellular 360 // devices (see crbug.com/321854). 361 if (type == ManagedState::MANAGED_TYPE_DEVICE || 362 requested_updates.find(path) == requested_updates.end()) 363 RequestProperties(type, path); 364 new_requested_updates.insert(path); 365 } 366 requested_updates.swap(new_requested_updates); 367} 368 369void ShillPropertyHandler::UpdateObserved(ManagedState::ManagedType type, 370 const base::ListValue& entries) { 371 DCHECK(type == ManagedState::MANAGED_TYPE_NETWORK || 372 type == ManagedState::MANAGED_TYPE_DEVICE); 373 ShillPropertyObserverMap& observer_map = 374 (type == ManagedState::MANAGED_TYPE_NETWORK) 375 ? observed_networks_ : observed_devices_; 376 ShillPropertyObserverMap new_observed; 377 for (base::ListValue::const_iterator iter1 = entries.begin(); 378 iter1 != entries.end(); ++iter1) { 379 std::string path; 380 (*iter1)->GetAsString(&path); 381 if (path.empty()) 382 continue; 383 ShillPropertyObserverMap::iterator iter2 = observer_map.find(path); 384 if (iter2 != observer_map.end()) { 385 new_observed[path] = iter2->second; 386 } else { 387 // Create an observer for future updates. 388 new_observed[path] = new ShillPropertyObserver( 389 type, path, base::Bind( 390 &ShillPropertyHandler::PropertyChangedCallback, AsWeakPtr())); 391 } 392 observer_map.erase(path); 393 // Limit the number of observed services. 394 if (new_observed.size() >= kMaxObserved) 395 break; 396 } 397 // Delete network service observers still in observer_map. 398 for (ShillPropertyObserverMap::iterator iter = observer_map.begin(); 399 iter != observer_map.end(); ++iter) { 400 delete iter->second; 401 } 402 observer_map.swap(new_observed); 403} 404 405void ShillPropertyHandler::UpdateAvailableTechnologies( 406 const base::ListValue& technologies) { 407 available_technologies_.clear(); 408 NET_LOG_EVENT("AvailableTechnologiesChanged", 409 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 410 for (base::ListValue::const_iterator iter = technologies.begin(); 411 iter != technologies.end(); ++iter) { 412 std::string technology; 413 (*iter)->GetAsString(&technology); 414 DCHECK(!technology.empty()); 415 available_technologies_.insert(technology); 416 } 417 listener_->TechnologyListChanged(); 418} 419 420void ShillPropertyHandler::UpdateEnabledTechnologies( 421 const base::ListValue& technologies) { 422 enabled_technologies_.clear(); 423 NET_LOG_EVENT("EnabledTechnologiesChanged", 424 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 425 for (base::ListValue::const_iterator iter = technologies.begin(); 426 iter != technologies.end(); ++iter) { 427 std::string technology; 428 (*iter)->GetAsString(&technology); 429 DCHECK(!technology.empty()); 430 enabled_technologies_.insert(technology); 431 enabling_technologies_.erase(technology); 432 } 433 listener_->TechnologyListChanged(); 434} 435 436void ShillPropertyHandler::UpdateUninitializedTechnologies( 437 const base::ListValue& technologies) { 438 uninitialized_technologies_.clear(); 439 NET_LOG_EVENT("UninitializedTechnologiesChanged", 440 base::StringPrintf("Size: %" PRIuS, technologies.GetSize())); 441 for (base::ListValue::const_iterator iter = technologies.begin(); 442 iter != technologies.end(); ++iter) { 443 std::string technology; 444 (*iter)->GetAsString(&technology); 445 DCHECK(!technology.empty()); 446 uninitialized_technologies_.insert(technology); 447 } 448 listener_->TechnologyListChanged(); 449} 450 451void ShillPropertyHandler::EnableTechnologyFailed( 452 const std::string& technology, 453 const network_handler::ErrorCallback& error_callback, 454 const std::string& dbus_error_name, 455 const std::string& dbus_error_message) { 456 enabling_technologies_.erase(technology); 457 network_handler::ShillErrorCallbackFunction( 458 "EnableTechnology Failed", 459 technology, error_callback, 460 dbus_error_name, dbus_error_message); 461} 462 463void ShillPropertyHandler::GetPropertiesCallback( 464 ManagedState::ManagedType type, 465 const std::string& path, 466 DBusMethodCallStatus call_status, 467 const base::DictionaryValue& properties) { 468 VLOG(2) << "GetPropertiesCallback: " << type << " : " << path; 469 pending_updates_[type].erase(path); 470 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 471 // The shill service no longer exists. This can happen when a network 472 // has been removed. 473 NET_LOG_DEBUG("Failed to get properties", 474 base::StringPrintf("%s: %d", path.c_str(), call_status)); 475 return; 476 } 477 // Update Favorite properties for networks in the Services list. 478 if (type == ManagedState::MANAGED_TYPE_NETWORK) { 479 // Only networks with a ProfilePath set are Favorites. 480 std::string profile_path; 481 properties.GetStringWithoutPathExpansion( 482 shill::kProfileProperty, &profile_path); 483 if (!profile_path.empty()) { 484 listener_->UpdateManagedStateProperties( 485 ManagedState::MANAGED_TYPE_FAVORITE, path, properties); 486 } 487 } 488 listener_->UpdateManagedStateProperties(type, path, properties); 489 // Request IPConfig parameters for networks. 490 if (type == ManagedState::MANAGED_TYPE_NETWORK && 491 properties.HasKey(shill::kIPConfigProperty)) { 492 std::string ip_config_path; 493 if (properties.GetString(shill::kIPConfigProperty, &ip_config_path)) { 494 DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties( 495 dbus::ObjectPath(ip_config_path), 496 base::Bind(&ShillPropertyHandler::GetIPConfigCallback, 497 AsWeakPtr(), path)); 498 } 499 } 500 501 // Notify the listener only when all updates for that type have completed. 502 if (pending_updates_[type].size() == 0) { 503 listener_->ManagedStateListChanged(type); 504 // Notify that Favorites have changed when notifying for Networks if there 505 // are no additional Favorite updates pending. 506 if (type == ManagedState::MANAGED_TYPE_NETWORK && 507 pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) { 508 listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE); 509 } 510 } 511} 512 513void ShillPropertyHandler::PropertyChangedCallback( 514 ManagedState::ManagedType type, 515 const std::string& path, 516 const std::string& key, 517 const base::Value& value) { 518 if (type == ManagedState::MANAGED_TYPE_NETWORK) 519 NetworkServicePropertyChangedCallback(path, key, value); 520 else if (type == ManagedState::MANAGED_TYPE_DEVICE) 521 NetworkDevicePropertyChangedCallback(path, key, value); 522 else 523 NOTREACHED(); 524} 525 526void ShillPropertyHandler::NetworkServicePropertyChangedCallback( 527 const std::string& path, 528 const std::string& key, 529 const base::Value& value) { 530 if (key == shill::kIPConfigProperty) { 531 // Request the IPConfig for the network and update network properties 532 // when the request completes. 533 std::string ip_config_path; 534 value.GetAsString(&ip_config_path); 535 DCHECK(!ip_config_path.empty()); 536 DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties( 537 dbus::ObjectPath(ip_config_path), 538 base::Bind(&ShillPropertyHandler::GetIPConfigCallback, 539 AsWeakPtr(), path)); 540 } else { 541 listener_->UpdateNetworkServiceProperty(path, key, value); 542 } 543} 544 545void ShillPropertyHandler::GetIPConfigCallback( 546 const std::string& service_path, 547 DBusMethodCallStatus call_status, 548 const base::DictionaryValue& properties) { 549 if (call_status != DBUS_METHOD_CALL_SUCCESS) { 550 NET_LOG_ERROR("Failed to get IP Config properties", 551 base::StringPrintf("%s: %d", 552 service_path.c_str(), call_status)); 553 return; 554 } 555 UpdateIPConfigProperty(service_path, properties, shill::kAddressProperty); 556 UpdateIPConfigProperty(service_path, properties, shill::kNameServersProperty); 557 UpdateIPConfigProperty(service_path, properties, shill::kPrefixlenProperty); 558 UpdateIPConfigProperty(service_path, properties, shill::kGatewayProperty); 559 UpdateIPConfigProperty(service_path, properties, 560 shill::kWebProxyAutoDiscoveryUrlProperty); 561} 562 563void ShillPropertyHandler::UpdateIPConfigProperty( 564 const std::string& service_path, 565 const base::DictionaryValue& properties, 566 const char* property) { 567 const base::Value* value; 568 if (!properties.GetWithoutPathExpansion(property, &value)) { 569 LOG(ERROR) << "Failed to get IPConfig property: " << property 570 << ", for: " << service_path; 571 return; 572 } 573 listener_->UpdateNetworkServiceProperty( 574 service_path, NetworkState::IPConfigProperty(property), *value); 575} 576 577void ShillPropertyHandler::NetworkDevicePropertyChangedCallback( 578 const std::string& path, 579 const std::string& key, 580 const base::Value& value) { 581 listener_->UpdateDeviceProperty(path, key, value); 582} 583 584} // namespace internal 585} // namespace chromeos 586