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