shill_property_handler.cc revision c2db58bd994c04d98e4ee2cd7565b71548655fe3
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      flimflam::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  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
239void 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
278void 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
298bool 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
357void 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
380void 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
416void 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
430void 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
445void 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
459void 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
471void 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
519void 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
532void 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
551void 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}
570
571void ShillPropertyHandler::UpdateIPConfigProperty(
572    const std::string& service_path,
573    const base::DictionaryValue& properties,
574    const char* property) {
575  const base::Value* value;
576  if (!properties.GetWithoutPathExpansion(property, &value)) {
577    LOG(ERROR) << "Failed to get IPConfig property: " << property
578               << ", for: " << service_path;
579    return;
580  }
581  listener_->UpdateNetworkServiceProperty(
582      service_path, NetworkState::IPConfigProperty(property), *value);
583}
584
585void ShillPropertyHandler::NetworkDevicePropertyChangedCallback(
586    const std::string& path,
587    const std::string& key,
588    const base::Value& value) {
589  listener_->UpdateDeviceProperty(path, key, value);
590}
591
592}  // namespace internal
593}  // namespace chromeos
594