Home | History | Annotate | Download | only in dbus
      1 // Copyright (c) 2013 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/dbus/shill_service_client_stub.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/stl_util.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/values.h"
     13 #include "chromeos/chromeos_switches.h"
     14 #include "chromeos/dbus/dbus_thread_manager.h"
     15 #include "chromeos/dbus/shill_manager_client.h"
     16 #include "chromeos/dbus/shill_profile_client_stub.h"
     17 #include "chromeos/dbus/shill_property_changed_observer.h"
     18 #include "dbus/bus.h"
     19 #include "dbus/message.h"
     20 #include "dbus/object_proxy.h"
     21 #include "third_party/cros_system_api/dbus/service_constants.h"
     22 
     23 namespace chromeos {
     24 
     25 namespace {
     26 
     27 const char kStubPortalledWifiPath[] = "portalled_wifi";
     28 const char kStubPortalledWifiName[] = "Portalled Wifi";
     29 
     30 void ErrorFunction(const std::string& error_name,
     31                    const std::string& error_message) {
     32   LOG(ERROR) << "Shill Error: " << error_name << " : " << error_message;
     33 }
     34 
     35 void PassStubListValue(const ShillServiceClient::ListValueCallback& callback,
     36                        base::ListValue* value) {
     37   callback.Run(*value);
     38 }
     39 
     40 void PassStubServiceProperties(
     41     const ShillServiceClient::DictionaryValueCallback& callback,
     42     DBusMethodCallStatus call_status,
     43     const base::DictionaryValue* properties) {
     44   callback.Run(call_status, *properties);
     45 }
     46 
     47 }  // namespace
     48 
     49 ShillServiceClientStub::ShillServiceClientStub() : weak_ptr_factory_(this) {
     50 }
     51 
     52 ShillServiceClientStub::~ShillServiceClientStub() {
     53   STLDeleteContainerPairSecondPointers(
     54       observer_list_.begin(), observer_list_.end());
     55 }
     56 
     57 // static
     58 bool ShillServiceClientStub::IsStubPortalledWifiEnabled(
     59     const std::string& path) {
     60   if (!CommandLine::ForCurrentProcess()->HasSwitch(
     61           chromeos::switches::kEnableStubPortalledWifi)) {
     62     return false;
     63   }
     64   return path == kStubPortalledWifiPath;
     65 }
     66 
     67 // ShillServiceClient overrides.
     68 
     69 void ShillServiceClientStub::AddPropertyChangedObserver(
     70     const dbus::ObjectPath& service_path,
     71     ShillPropertyChangedObserver* observer) {
     72   GetObserverList(service_path).AddObserver(observer);
     73 }
     74 
     75 void ShillServiceClientStub::RemovePropertyChangedObserver(
     76     const dbus::ObjectPath& service_path,
     77     ShillPropertyChangedObserver* observer) {
     78   GetObserverList(service_path).RemoveObserver(observer);
     79 }
     80 
     81 void ShillServiceClientStub::GetProperties(
     82     const dbus::ObjectPath& service_path,
     83     const DictionaryValueCallback& callback) {
     84   base::DictionaryValue* nested_dict = NULL;
     85   scoped_ptr<base::DictionaryValue> result_properties;
     86   DBusMethodCallStatus call_status;
     87   stub_services_.GetDictionaryWithoutPathExpansion(service_path.value(),
     88                                                    &nested_dict);
     89   if (nested_dict) {
     90     result_properties.reset(nested_dict->DeepCopy());
     91     // Remove credentials that Shill wouldn't send.
     92     result_properties->RemoveWithoutPathExpansion(flimflam::kPassphraseProperty,
     93                                                   NULL);
     94     call_status = DBUS_METHOD_CALL_SUCCESS;
     95   } else {
     96     result_properties.reset(new base::DictionaryValue);
     97     call_status = DBUS_METHOD_CALL_FAILURE;
     98   }
     99 
    100   base::MessageLoop::current()->PostTask(
    101       FROM_HERE,
    102       base::Bind(&PassStubServiceProperties,
    103                  callback,
    104                  call_status,
    105                  base::Owned(result_properties.release())));
    106 }
    107 
    108 void ShillServiceClientStub::SetProperty(const dbus::ObjectPath& service_path,
    109                                          const std::string& name,
    110                                          const base::Value& value,
    111                                          const base::Closure& callback,
    112                                          const ErrorCallback& error_callback) {
    113   if (!SetServiceProperty(service_path.value(), name, value)) {
    114     LOG(ERROR) << "Service not found: " << service_path.value();
    115     error_callback.Run("Error.InvalidService", "Invalid Service");
    116     return;
    117   }
    118   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    119 }
    120 
    121 void ShillServiceClientStub::SetProperties(
    122     const dbus::ObjectPath& service_path,
    123     const base::DictionaryValue& properties,
    124     const base::Closure& callback,
    125     const ErrorCallback& error_callback) {
    126   for (base::DictionaryValue::Iterator iter(properties);
    127        !iter.IsAtEnd(); iter.Advance()) {
    128     if (!SetServiceProperty(service_path.value(), iter.key(), iter.value())) {
    129       LOG(ERROR) << "Service not found: " << service_path.value();
    130       error_callback.Run("Error.InvalidService", "Invalid Service");
    131       return;
    132     }
    133   }
    134   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    135 }
    136 
    137 void ShillServiceClientStub::ClearProperty(
    138     const dbus::ObjectPath& service_path,
    139     const std::string& name,
    140     const base::Closure& callback,
    141     const ErrorCallback& error_callback) {
    142   base::DictionaryValue* dict = NULL;
    143   if (!stub_services_.GetDictionaryWithoutPathExpansion(
    144       service_path.value(), &dict)) {
    145     error_callback.Run("Error.InvalidService", "Invalid Service");
    146     return;
    147   }
    148   dict->RemoveWithoutPathExpansion(name, NULL);
    149   // Note: Shill does not send notifications when properties are cleared.
    150   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    151 }
    152 
    153 void ShillServiceClientStub::ClearProperties(
    154     const dbus::ObjectPath& service_path,
    155     const std::vector<std::string>& names,
    156     const ListValueCallback& callback,
    157     const ErrorCallback& error_callback) {
    158   base::DictionaryValue* dict = NULL;
    159   if (!stub_services_.GetDictionaryWithoutPathExpansion(
    160       service_path.value(), &dict)) {
    161     error_callback.Run("Error.InvalidService", "Invalid Service");
    162     return;
    163   }
    164   scoped_ptr<base::ListValue> results(new base::ListValue);
    165   for (std::vector<std::string>::const_iterator iter = names.begin();
    166       iter != names.end(); ++iter) {
    167     dict->RemoveWithoutPathExpansion(*iter, NULL);
    168     // Note: Shill does not send notifications when properties are cleared.
    169     results->AppendBoolean(true);
    170   }
    171   base::MessageLoop::current()->PostTask(
    172       FROM_HERE,
    173       base::Bind(&PassStubListValue,
    174                  callback, base::Owned(results.release())));
    175 }
    176 
    177 void ShillServiceClientStub::Connect(const dbus::ObjectPath& service_path,
    178                                      const base::Closure& callback,
    179                                      const ErrorCallback& error_callback) {
    180   VLOG(1) << "ShillServiceClientStub::Connect: " << service_path.value();
    181   base::DictionaryValue* service_properties;
    182   if (!stub_services_.GetDictionary(
    183           service_path.value(), &service_properties)) {
    184     LOG(ERROR) << "Service not found: " << service_path.value();
    185     error_callback.Run("Error.InvalidService", "Invalid Service");
    186     return;
    187   }
    188 
    189   // Set any other services of the same Type to 'offline' first, before setting
    190   // State to Association which will trigger sorting Manager.Services and
    191   // sending an update.
    192   SetOtherServicesOffline(service_path.value());
    193 
    194   // Set Associating.
    195   base::StringValue associating_value(flimflam::kStateAssociation);
    196   SetServiceProperty(service_path.value(),
    197                      flimflam::kStateProperty,
    198                      associating_value);
    199 
    200   // Set Online after a delay.
    201   base::TimeDelta delay;
    202   if (CommandLine::ForCurrentProcess()->HasSwitch(
    203           chromeos::switches::kEnableStubInteractive)) {
    204     const int kConnectDelaySeconds = 5;
    205     delay = base::TimeDelta::FromSeconds(kConnectDelaySeconds);
    206   }
    207   base::StringValue online_value(flimflam::kStateOnline);
    208   if (service_path.value() == kStubPortalledWifiPath)
    209     online_value = base::StringValue(flimflam::kStatePortal);
    210   std::string passphrase;
    211   service_properties->GetStringWithoutPathExpansion(
    212       flimflam::kPassphraseProperty, &passphrase);
    213   if (passphrase == "failure")
    214     online_value = base::StringValue(flimflam::kStateFailure);
    215   base::MessageLoop::current()->PostDelayedTask(
    216       FROM_HERE,
    217       base::Bind(&ShillServiceClientStub::SetProperty,
    218                  weak_ptr_factory_.GetWeakPtr(),
    219                  service_path,
    220                  flimflam::kStateProperty,
    221                  online_value,
    222                  base::Bind(&base::DoNothing),
    223                  error_callback),
    224       delay);
    225   callback.Run();
    226   // On failure, also set the Error property.
    227   if (passphrase == "failure") {
    228     base::MessageLoop::current()->PostDelayedTask(
    229         FROM_HERE,
    230         base::Bind(&ShillServiceClientStub::SetProperty,
    231                    weak_ptr_factory_.GetWeakPtr(),
    232                    service_path,
    233                    flimflam::kErrorProperty,
    234                    base::StringValue(flimflam::kErrorBadPassphrase),
    235                    base::Bind(&base::DoNothing),
    236                    error_callback),
    237         delay);
    238   }
    239 }
    240 
    241 void ShillServiceClientStub::Disconnect(const dbus::ObjectPath& service_path,
    242                                         const base::Closure& callback,
    243                                         const ErrorCallback& error_callback) {
    244   base::Value* service;
    245   if (!stub_services_.Get(service_path.value(), &service)) {
    246     error_callback.Run("Error.InvalidService", "Invalid Service");
    247     return;
    248   }
    249   base::TimeDelta delay;
    250   if (CommandLine::ForCurrentProcess()->HasSwitch(
    251           chromeos::switches::kEnableStubInteractive)) {
    252     const int kConnectDelaySeconds = 2;
    253     delay = base::TimeDelta::FromSeconds(kConnectDelaySeconds);
    254   }
    255   // Set Idle after a delay
    256   base::StringValue idle_value(flimflam::kStateIdle);
    257   base::MessageLoop::current()->PostDelayedTask(
    258       FROM_HERE,
    259       base::Bind(&ShillServiceClientStub::SetProperty,
    260                  weak_ptr_factory_.GetWeakPtr(),
    261                  service_path,
    262                  flimflam::kStateProperty,
    263                  idle_value,
    264                  base::Bind(&base::DoNothing),
    265                  error_callback),
    266       delay);
    267   callback.Run();
    268 }
    269 
    270 void ShillServiceClientStub::Remove(const dbus::ObjectPath& service_path,
    271                                     const base::Closure& callback,
    272                                     const ErrorCallback& error_callback) {
    273   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    274 }
    275 
    276 void ShillServiceClientStub::ActivateCellularModem(
    277     const dbus::ObjectPath& service_path,
    278     const std::string& carrier,
    279     const base::Closure& callback,
    280     const ErrorCallback& error_callback) {
    281   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    282 }
    283 
    284 void ShillServiceClientStub::CompleteCellularActivation(
    285     const dbus::ObjectPath& service_path,
    286     const base::Closure& callback,
    287     const ErrorCallback& error_callback) {
    288   base::MessageLoop::current()->PostTask(FROM_HERE, callback);
    289 }
    290 
    291 bool ShillServiceClientStub::CallActivateCellularModemAndBlock(
    292     const dbus::ObjectPath& service_path,
    293     const std::string& carrier) {
    294   return true;
    295 }
    296 
    297 void ShillServiceClientStub::GetLoadableProfileEntries(
    298     const dbus::ObjectPath& service_path,
    299     const DictionaryValueCallback& callback) {
    300   // Provide a dictionary with a single { profile_path, service_path } entry
    301   // if the Profile property is set, or an empty dictionary.
    302   scoped_ptr<base::DictionaryValue> result_properties(
    303       new base::DictionaryValue);
    304   base::DictionaryValue* service_properties =
    305       GetModifiableServiceProperties(service_path.value());
    306   if (service_properties) {
    307     std::string profile_path;
    308     if (service_properties->GetStringWithoutPathExpansion(
    309             flimflam::kProfileProperty, &profile_path)) {
    310       result_properties->SetStringWithoutPathExpansion(
    311           profile_path, service_path.value());
    312     }
    313   } else {
    314     LOG(WARNING) << "Service not in profile: " << service_path.value();
    315   }
    316 
    317   DBusMethodCallStatus call_status = DBUS_METHOD_CALL_SUCCESS;
    318   base::MessageLoop::current()->PostTask(
    319       FROM_HERE,
    320       base::Bind(&PassStubServiceProperties,
    321                  callback,
    322                  call_status,
    323                  base::Owned(result_properties.release())));
    324 }
    325 
    326 ShillServiceClient::TestInterface* ShillServiceClientStub::GetTestInterface() {
    327   return this;
    328 }
    329 
    330 // ShillServiceClient::TestInterface overrides.
    331 
    332 void ShillServiceClientStub::AddService(const std::string& service_path,
    333                                         const std::string& name,
    334                                         const std::string& type,
    335                                         const std::string& state,
    336                                         bool add_to_visible_list,
    337                                         bool add_to_watch_list) {
    338   std::string nstate = state;
    339   if (CommandLine::ForCurrentProcess()->HasSwitch(
    340           chromeos::switches::kDefaultStubNetworkStateIdle)) {
    341     nstate = flimflam::kStateIdle;
    342   }
    343   AddServiceWithIPConfig(service_path, name, type, nstate, "",
    344                          add_to_visible_list, add_to_watch_list);
    345 }
    346 
    347 void ShillServiceClientStub::AddServiceWithIPConfig(
    348     const std::string& service_path,
    349     const std::string& name,
    350     const std::string& type,
    351     const std::string& state,
    352     const std::string& ipconfig_path,
    353     bool add_to_visible_list,
    354     bool add_to_watch_list) {
    355   DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
    356       AddManagerService(service_path, add_to_visible_list, add_to_watch_list);
    357 
    358   base::DictionaryValue* properties =
    359       GetModifiableServiceProperties(service_path);
    360   properties->SetWithoutPathExpansion(
    361       flimflam::kSSIDProperty,
    362       base::Value::CreateStringValue(service_path));
    363   properties->SetWithoutPathExpansion(
    364       flimflam::kNameProperty,
    365       base::Value::CreateStringValue(name));
    366   properties->SetWithoutPathExpansion(
    367       flimflam::kTypeProperty,
    368       base::Value::CreateStringValue(type));
    369   properties->SetWithoutPathExpansion(
    370       flimflam::kStateProperty,
    371       base::Value::CreateStringValue(state));
    372   if (!ipconfig_path.empty())
    373     properties->SetWithoutPathExpansion(
    374         shill::kIPConfigProperty,
    375         base::Value::CreateStringValue(ipconfig_path));
    376 }
    377 
    378 void ShillServiceClientStub::RemoveService(const std::string& service_path) {
    379   DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
    380       RemoveManagerService(service_path);
    381 
    382   stub_services_.RemoveWithoutPathExpansion(service_path, NULL);
    383 }
    384 
    385 bool ShillServiceClientStub::SetServiceProperty(const std::string& service_path,
    386                                                 const std::string& property,
    387                                                 const base::Value& value) {
    388   base::DictionaryValue* dict = NULL;
    389   if (!stub_services_.GetDictionaryWithoutPathExpansion(service_path, &dict))
    390     return false;
    391 
    392   VLOG(1) << "Service.SetProperty: " << property << " = " << value
    393           << " For: " << service_path;
    394 
    395   base::DictionaryValue new_properties;
    396   std::string changed_property;
    397   bool case_sensitive = true;
    398   if (StartsWithASCII(property, "Provider.", case_sensitive) ||
    399       StartsWithASCII(property, "OpenVPN.", case_sensitive) ||
    400       StartsWithASCII(property, "L2TPIPsec.", case_sensitive)) {
    401     // These properties are only nested within the Provider dictionary if read
    402     // from Shill.
    403     base::DictionaryValue* provider = new base::DictionaryValue;
    404     provider->SetWithoutPathExpansion(property, value.DeepCopy());
    405     new_properties.SetWithoutPathExpansion(flimflam::kProviderProperty,
    406                                            provider);
    407     changed_property = flimflam::kProviderProperty;
    408   } else {
    409     new_properties.SetWithoutPathExpansion(property, value.DeepCopy());
    410     changed_property = property;
    411   }
    412 
    413   dict->MergeDictionary(&new_properties);
    414 
    415   if (property == flimflam::kStateProperty) {
    416     // When State changes the sort order of Services may change.
    417     DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
    418         SortManagerServices();
    419   }
    420 
    421   base::MessageLoop::current()->PostTask(
    422       FROM_HERE,
    423       base::Bind(&ShillServiceClientStub::NotifyObserversPropertyChanged,
    424                  weak_ptr_factory_.GetWeakPtr(),
    425                  dbus::ObjectPath(service_path), changed_property));
    426   return true;
    427 }
    428 
    429 const base::DictionaryValue* ShillServiceClientStub::GetServiceProperties(
    430     const std::string& service_path) const {
    431   const base::DictionaryValue* properties = NULL;
    432   stub_services_.GetDictionaryWithoutPathExpansion(service_path, &properties);
    433   return properties;
    434 }
    435 
    436 void ShillServiceClientStub::ClearServices() {
    437   DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
    438       ClearManagerServices();
    439 
    440   stub_services_.Clear();
    441 }
    442 
    443 void ShillServiceClientStub::AddDefaultServices() {
    444   const bool add_to_visible = true;
    445   const bool add_to_watchlist = true;
    446 
    447   if (!CommandLine::ForCurrentProcess()->HasSwitch(
    448           chromeos::switches::kDisableStubEthernet)) {
    449     AddService("eth1", "eth1",
    450                flimflam::kTypeEthernet,
    451                flimflam::kStateOnline,
    452                add_to_visible, add_to_watchlist);
    453   }
    454 
    455   // Wifi
    456 
    457   AddService("wifi1", "wifi1",
    458              flimflam::kTypeWifi,
    459              flimflam::kStateOnline,
    460              add_to_visible, add_to_watchlist);
    461   SetServiceProperty("wifi1",
    462                      flimflam::kSecurityProperty,
    463                      base::StringValue(flimflam::kSecurityWep));
    464 
    465   AddService("wifi2", "wifi2_PSK",
    466              flimflam::kTypeWifi,
    467              flimflam::kStateIdle,
    468              add_to_visible, add_to_watchlist);
    469   SetServiceProperty("wifi2",
    470                      flimflam::kSecurityProperty,
    471                      base::StringValue(flimflam::kSecurityPsk));
    472   base::FundamentalValue strength_value(80);
    473   SetServiceProperty("wifi2",
    474                      flimflam::kSignalStrengthProperty,
    475                      strength_value);
    476 
    477   if (CommandLine::ForCurrentProcess()->HasSwitch(
    478           chromeos::switches::kEnableStubPortalledWifi)) {
    479     AddService(kStubPortalledWifiPath, kStubPortalledWifiName,
    480                flimflam::kTypeWifi,
    481                flimflam::kStatePortal,
    482                add_to_visible, add_to_watchlist);
    483     SetServiceProperty(kStubPortalledWifiPath,
    484                        flimflam::kSecurityProperty,
    485                        base::StringValue(flimflam::kSecurityNone));
    486   }
    487 
    488   // Wimax
    489 
    490   AddService("wimax1", "wimax1",
    491              flimflam::kTypeWimax,
    492              flimflam::kStateIdle,
    493              add_to_visible, add_to_watchlist);
    494   SetServiceProperty("wimax1",
    495                      flimflam::kConnectableProperty,
    496                      base::FundamentalValue(true));
    497 
    498   // Cellular
    499 
    500   AddService("cellular1", "cellular1",
    501              flimflam::kTypeCellular,
    502              flimflam::kStateIdle,
    503              add_to_visible, add_to_watchlist);
    504   base::StringValue technology_value(flimflam::kNetworkTechnologyGsm);
    505   SetServiceProperty("cellular1",
    506                      flimflam::kNetworkTechnologyProperty,
    507                      technology_value);
    508   SetServiceProperty("cellular1",
    509                      flimflam::kActivationStateProperty,
    510                      base::StringValue(flimflam::kActivationStateNotActivated));
    511   SetServiceProperty("cellular1",
    512                      flimflam::kRoamingStateProperty,
    513                      base::StringValue(flimflam::kRoamingStateHome));
    514 
    515   // VPN
    516 
    517   // Set the "Provider" dictionary properties. Note: when setting these in
    518   // Shill, "Provider.Type", etc keys are used, but when reading the values
    519   // "Provider" . "Type", etc keys are used. Here we are setting the values
    520   // that will be read (by the UI, tests, etc).
    521   base::DictionaryValue provider_properties;
    522   provider_properties.SetString(flimflam::kTypeProperty,
    523                                 flimflam::kProviderOpenVpn);
    524   provider_properties.SetString(flimflam::kHostProperty, "vpn_host");
    525 
    526   AddService("vpn1", "vpn1",
    527              flimflam::kTypeVPN,
    528              flimflam::kStateOnline,
    529              add_to_visible, add_to_watchlist);
    530   SetServiceProperty("vpn1",
    531                      flimflam::kProviderProperty,
    532                      provider_properties);
    533 
    534   AddService("vpn2", "vpn2",
    535              flimflam::kTypeVPN,
    536              flimflam::kStateOffline,
    537              add_to_visible, add_to_watchlist);
    538   SetServiceProperty("vpn2",
    539                      flimflam::kProviderProperty,
    540                      provider_properties);
    541 
    542   DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()->
    543       AddService(ShillProfileClientStub::kSharedProfilePath, "wifi2");
    544 
    545   DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
    546       SortManagerServices();
    547 }
    548 
    549 void ShillServiceClientStub::NotifyObserversPropertyChanged(
    550     const dbus::ObjectPath& service_path,
    551     const std::string& property) {
    552   base::DictionaryValue* dict = NULL;
    553   std::string path = service_path.value();
    554   if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &dict)) {
    555     LOG(ERROR) << "Notify for unknown service: " << path;
    556     return;
    557   }
    558   base::Value* value = NULL;
    559   if (!dict->GetWithoutPathExpansion(property, &value)) {
    560     LOG(ERROR) << "Notify for unknown property: "
    561                << path << " : " << property;
    562     return;
    563   }
    564   FOR_EACH_OBSERVER(ShillPropertyChangedObserver,
    565                     GetObserverList(service_path),
    566                     OnPropertyChanged(property, *value));
    567 }
    568 
    569 base::DictionaryValue* ShillServiceClientStub::GetModifiableServiceProperties(
    570     const std::string& service_path) {
    571   base::DictionaryValue* properties = NULL;
    572   if (!stub_services_.GetDictionaryWithoutPathExpansion(
    573       service_path, &properties)) {
    574     properties = new base::DictionaryValue;
    575     stub_services_.Set(service_path, properties);
    576   }
    577   return properties;
    578 }
    579 
    580 ShillServiceClientStub::PropertyObserverList&
    581 ShillServiceClientStub::GetObserverList(const dbus::ObjectPath& device_path) {
    582   std::map<dbus::ObjectPath, PropertyObserverList*>::iterator iter =
    583       observer_list_.find(device_path);
    584   if (iter != observer_list_.end())
    585     return *(iter->second);
    586   PropertyObserverList* observer_list = new PropertyObserverList();
    587   observer_list_[device_path] = observer_list;
    588   return *observer_list;
    589 }
    590 
    591 void ShillServiceClientStub::SetOtherServicesOffline(
    592     const std::string& service_path) {
    593   const base::DictionaryValue* service_properties = GetServiceProperties(
    594       service_path);
    595   if (!service_properties) {
    596     LOG(ERROR) << "Missing service: " << service_path;
    597     return;
    598   }
    599   std::string service_type;
    600   service_properties->GetString(flimflam::kTypeProperty, &service_type);
    601   // Set all other services of the same type to offline (Idle).
    602   for (base::DictionaryValue::Iterator iter(stub_services_);
    603        !iter.IsAtEnd(); iter.Advance()) {
    604     std::string path = iter.key();
    605     if (path == service_path)
    606       continue;
    607     base::DictionaryValue* properties;
    608     if (!stub_services_.GetDictionaryWithoutPathExpansion(path, &properties))
    609       NOTREACHED();
    610 
    611     std::string type;
    612     properties->GetString(flimflam::kTypeProperty, &type);
    613     if (type != service_type)
    614       continue;
    615     properties->SetWithoutPathExpansion(
    616         flimflam::kStateProperty,
    617         base::Value::CreateStringValue(flimflam::kStateIdle));
    618   }
    619 }
    620 
    621 }  // namespace chromeos
    622