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.
5 #include "chromeos/dbus/shill_service_client_stub.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"
19 #include "dbus/message.h"
20 #include "dbus/object_proxy.h"
21 #include "third_party/cros_system_api/dbus/service_constants.h"
27 const char kStubPortalledWifiPath
[] = "portalled_wifi";
28 const char kStubPortalledWifiName
[] = "Portalled Wifi";
30 void ErrorFunction(const std::string
& error_name
,
31 const std::string
& error_message
) {
32 LOG(ERROR
) << "Shill Error: " << error_name
<< " : " << error_message
;
35 void PassStubListValue(const ShillServiceClient::ListValueCallback
& callback
,
36 base::ListValue
* value
) {
40 void PassStubServiceProperties(
41 const ShillServiceClient::DictionaryValueCallback
& callback
,
42 DBusMethodCallStatus call_status
,
43 const base::DictionaryValue
* properties
) {
44 callback
.Run(call_status
, *properties
);
49 ShillServiceClientStub::ShillServiceClientStub() : weak_ptr_factory_(this) {
52 ShillServiceClientStub::~ShillServiceClientStub() {
53 STLDeleteContainerPairSecondPointers(
54 observer_list_
.begin(), observer_list_
.end());
58 bool ShillServiceClientStub::IsStubPortalledWifiEnabled(
59 const std::string
& path
) {
60 if (!CommandLine::ForCurrentProcess()->HasSwitch(
61 chromeos::switches::kEnableStubPortalledWifi
)) {
64 return path
== kStubPortalledWifiPath
;
67 // ShillServiceClient overrides.
69 void ShillServiceClientStub::Init(dbus::Bus
* bus
) {
72 void ShillServiceClientStub::AddPropertyChangedObserver(
73 const dbus::ObjectPath
& service_path
,
74 ShillPropertyChangedObserver
* observer
) {
75 GetObserverList(service_path
).AddObserver(observer
);
78 void ShillServiceClientStub::RemovePropertyChangedObserver(
79 const dbus::ObjectPath
& service_path
,
80 ShillPropertyChangedObserver
* observer
) {
81 GetObserverList(service_path
).RemoveObserver(observer
);
84 void ShillServiceClientStub::GetProperties(
85 const dbus::ObjectPath
& service_path
,
86 const DictionaryValueCallback
& callback
) {
87 base::DictionaryValue
* nested_dict
= NULL
;
88 scoped_ptr
<base::DictionaryValue
> result_properties
;
89 DBusMethodCallStatus call_status
;
90 stub_services_
.GetDictionaryWithoutPathExpansion(service_path
.value(),
93 result_properties
.reset(nested_dict
->DeepCopy());
94 // Remove credentials that Shill wouldn't send.
95 result_properties
->RemoveWithoutPathExpansion(flimflam::kPassphraseProperty
,
97 call_status
= DBUS_METHOD_CALL_SUCCESS
;
99 LOG(ERROR
) << "Properties not found for: " << service_path
.value();
100 result_properties
.reset(new base::DictionaryValue
);
101 call_status
= DBUS_METHOD_CALL_FAILURE
;
104 base::MessageLoop::current()->PostTask(
106 base::Bind(&PassStubServiceProperties
,
109 base::Owned(result_properties
.release())));
112 void ShillServiceClientStub::SetProperty(const dbus::ObjectPath
& service_path
,
113 const std::string
& name
,
114 const base::Value
& value
,
115 const base::Closure
& callback
,
116 const ErrorCallback
& error_callback
) {
117 if (!SetServiceProperty(service_path
.value(), name
, value
)) {
118 LOG(ERROR
) << "Service not found: " << service_path
.value();
119 error_callback
.Run("Error.InvalidService", "Invalid Service");
122 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
125 void ShillServiceClientStub::SetProperties(
126 const dbus::ObjectPath
& service_path
,
127 const base::DictionaryValue
& properties
,
128 const base::Closure
& callback
,
129 const ErrorCallback
& error_callback
) {
130 for (base::DictionaryValue::Iterator
iter(properties
);
131 !iter
.IsAtEnd(); iter
.Advance()) {
132 if (!SetServiceProperty(service_path
.value(), iter
.key(), iter
.value())) {
133 LOG(ERROR
) << "Service not found: " << service_path
.value();
134 error_callback
.Run("Error.InvalidService", "Invalid Service");
138 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
141 void ShillServiceClientStub::ClearProperty(
142 const dbus::ObjectPath
& service_path
,
143 const std::string
& name
,
144 const base::Closure
& callback
,
145 const ErrorCallback
& error_callback
) {
146 base::DictionaryValue
* dict
= NULL
;
147 if (!stub_services_
.GetDictionaryWithoutPathExpansion(
148 service_path
.value(), &dict
)) {
149 error_callback
.Run("Error.InvalidService", "Invalid Service");
152 dict
->RemoveWithoutPathExpansion(name
, NULL
);
153 // Note: Shill does not send notifications when properties are cleared.
154 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
157 void ShillServiceClientStub::ClearProperties(
158 const dbus::ObjectPath
& service_path
,
159 const std::vector
<std::string
>& names
,
160 const ListValueCallback
& callback
,
161 const ErrorCallback
& error_callback
) {
162 base::DictionaryValue
* dict
= NULL
;
163 if (!stub_services_
.GetDictionaryWithoutPathExpansion(
164 service_path
.value(), &dict
)) {
165 error_callback
.Run("Error.InvalidService", "Invalid Service");
168 scoped_ptr
<base::ListValue
> results(new base::ListValue
);
169 for (std::vector
<std::string
>::const_iterator iter
= names
.begin();
170 iter
!= names
.end(); ++iter
) {
171 dict
->RemoveWithoutPathExpansion(*iter
, NULL
);
172 // Note: Shill does not send notifications when properties are cleared.
173 results
->AppendBoolean(true);
175 base::MessageLoop::current()->PostTask(
177 base::Bind(&PassStubListValue
,
178 callback
, base::Owned(results
.release())));
181 void ShillServiceClientStub::Connect(const dbus::ObjectPath
& service_path
,
182 const base::Closure
& callback
,
183 const ErrorCallback
& error_callback
) {
184 VLOG(1) << "ShillServiceClientStub::Connect: " << service_path
.value();
185 base::DictionaryValue
* service_properties
;
186 if (!stub_services_
.GetDictionary(
187 service_path
.value(), &service_properties
)) {
188 LOG(ERROR
) << "Service not found: " << service_path
.value();
189 error_callback
.Run("Error.InvalidService", "Invalid Service");
193 // Set any other services of the same Type to 'offline' first, before setting
194 // State to Association which will trigger sorting Manager.Services and
195 // sending an update.
196 SetOtherServicesOffline(service_path
.value());
199 base::StringValue
associating_value(flimflam::kStateAssociation
);
200 SetServiceProperty(service_path
.value(),
201 flimflam::kStateProperty
,
204 // Set Online after a delay.
205 base::TimeDelta delay
;
206 if (CommandLine::ForCurrentProcess()->HasSwitch(
207 chromeos::switches::kEnableStubInteractive
)) {
208 const int kConnectDelaySeconds
= 5;
209 delay
= base::TimeDelta::FromSeconds(kConnectDelaySeconds
);
211 base::StringValue
online_value(flimflam::kStateOnline
);
212 if (service_path
.value() == kStubPortalledWifiPath
)
213 online_value
= base::StringValue(flimflam::kStatePortal
);
214 std::string passphrase
;
215 service_properties
->GetStringWithoutPathExpansion(
216 flimflam::kPassphraseProperty
, &passphrase
);
217 if (passphrase
== "failure")
218 online_value
= base::StringValue(flimflam::kStateFailure
);
219 base::MessageLoop::current()->PostDelayedTask(
221 base::Bind(&ShillServiceClientStub::SetProperty
,
222 weak_ptr_factory_
.GetWeakPtr(),
224 flimflam::kStateProperty
,
226 base::Bind(&base::DoNothing
),
230 // On failure, also set the Error property.
231 if (passphrase
== "failure") {
232 base::MessageLoop::current()->PostDelayedTask(
234 base::Bind(&ShillServiceClientStub::SetProperty
,
235 weak_ptr_factory_
.GetWeakPtr(),
237 flimflam::kErrorProperty
,
238 base::StringValue(flimflam::kErrorBadPassphrase
),
239 base::Bind(&base::DoNothing
),
245 void ShillServiceClientStub::Disconnect(const dbus::ObjectPath
& service_path
,
246 const base::Closure
& callback
,
247 const ErrorCallback
& error_callback
) {
248 base::Value
* service
;
249 if (!stub_services_
.Get(service_path
.value(), &service
)) {
250 error_callback
.Run("Error.InvalidService", "Invalid Service");
253 base::TimeDelta delay
;
254 if (CommandLine::ForCurrentProcess()->HasSwitch(
255 chromeos::switches::kEnableStubInteractive
)) {
256 const int kConnectDelaySeconds
= 2;
257 delay
= base::TimeDelta::FromSeconds(kConnectDelaySeconds
);
259 // Set Idle after a delay
260 base::StringValue
idle_value(flimflam::kStateIdle
);
261 base::MessageLoop::current()->PostDelayedTask(
263 base::Bind(&ShillServiceClientStub::SetProperty
,
264 weak_ptr_factory_
.GetWeakPtr(),
266 flimflam::kStateProperty
,
268 base::Bind(&base::DoNothing
),
274 void ShillServiceClientStub::Remove(const dbus::ObjectPath
& service_path
,
275 const base::Closure
& callback
,
276 const ErrorCallback
& error_callback
) {
277 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
280 void ShillServiceClientStub::ActivateCellularModem(
281 const dbus::ObjectPath
& service_path
,
282 const std::string
& carrier
,
283 const base::Closure
& callback
,
284 const ErrorCallback
& error_callback
) {
285 base::DictionaryValue
* service_properties
=
286 GetModifiableServiceProperties(service_path
.value());
287 if (!service_properties
) {
288 LOG(ERROR
) << "Service not found: " << service_path
.value();
289 error_callback
.Run("Error.InvalidService", "Invalid Service");
291 SetServiceProperty(service_path
.value(),
292 flimflam::kActivationStateProperty
,
293 base::StringValue(flimflam::kActivationStateActivating
));
294 base::TimeDelta delay
;
295 if (CommandLine::ForCurrentProcess()->HasSwitch(
296 chromeos::switches::kEnableStubInteractive
)) {
297 const int kConnectDelaySeconds
= 2;
298 delay
= base::TimeDelta::FromSeconds(kConnectDelaySeconds
);
300 // Set Activated after a delay
301 base::MessageLoop::current()->PostDelayedTask(
303 base::Bind(&ShillServiceClientStub::SetCellularActivated
,
304 weak_ptr_factory_
.GetWeakPtr(),
309 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
312 void ShillServiceClientStub::CompleteCellularActivation(
313 const dbus::ObjectPath
& service_path
,
314 const base::Closure
& callback
,
315 const ErrorCallback
& error_callback
) {
316 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
319 bool ShillServiceClientStub::CallActivateCellularModemAndBlock(
320 const dbus::ObjectPath
& service_path
,
321 const std::string
& carrier
) {
325 void ShillServiceClientStub::GetLoadableProfileEntries(
326 const dbus::ObjectPath
& service_path
,
327 const DictionaryValueCallback
& callback
) {
328 // Provide a dictionary with a single { profile_path, service_path } entry
329 // if the Profile property is set, or an empty dictionary.
330 scoped_ptr
<base::DictionaryValue
> result_properties(
331 new base::DictionaryValue
);
332 base::DictionaryValue
* service_properties
=
333 GetModifiableServiceProperties(service_path
.value());
334 if (service_properties
) {
335 std::string profile_path
;
336 if (service_properties
->GetStringWithoutPathExpansion(
337 flimflam::kProfileProperty
, &profile_path
)) {
338 result_properties
->SetStringWithoutPathExpansion(
339 profile_path
, service_path
.value());
342 LOG(WARNING
) << "Service not in profile: " << service_path
.value();
345 DBusMethodCallStatus call_status
= DBUS_METHOD_CALL_SUCCESS
;
346 base::MessageLoop::current()->PostTask(
348 base::Bind(&PassStubServiceProperties
,
351 base::Owned(result_properties
.release())));
354 ShillServiceClient::TestInterface
* ShillServiceClientStub::GetTestInterface() {
358 // ShillServiceClient::TestInterface overrides.
360 void ShillServiceClientStub::AddService(const std::string
& service_path
,
361 const std::string
& name
,
362 const std::string
& type
,
363 const std::string
& state
,
364 bool add_to_visible_list
,
365 bool add_to_watch_list
) {
366 std::string nstate
= state
;
367 if (CommandLine::ForCurrentProcess()->HasSwitch(
368 chromeos::switches::kDefaultStubNetworkStateIdle
)) {
369 nstate
= flimflam::kStateIdle
;
371 AddServiceWithIPConfig(service_path
, name
, type
, nstate
, "",
372 add_to_visible_list
, add_to_watch_list
);
375 void ShillServiceClientStub::AddServiceWithIPConfig(
376 const std::string
& service_path
,
377 const std::string
& name
,
378 const std::string
& type
,
379 const std::string
& state
,
380 const std::string
& ipconfig_path
,
381 bool add_to_visible_list
,
382 bool add_to_watch_list
) {
383 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
384 AddManagerService(service_path
, add_to_visible_list
, add_to_watch_list
);
386 base::DictionaryValue
* properties
=
387 GetModifiableServiceProperties(service_path
);
388 properties
->SetWithoutPathExpansion(
389 flimflam::kSSIDProperty
,
390 base::Value::CreateStringValue(service_path
));
391 properties
->SetWithoutPathExpansion(
392 flimflam::kNameProperty
,
393 base::Value::CreateStringValue(name
));
394 properties
->SetWithoutPathExpansion(
395 flimflam::kTypeProperty
,
396 base::Value::CreateStringValue(type
));
397 properties
->SetWithoutPathExpansion(
398 flimflam::kStateProperty
,
399 base::Value::CreateStringValue(state
));
400 if (!ipconfig_path
.empty())
401 properties
->SetWithoutPathExpansion(
402 shill::kIPConfigProperty
,
403 base::Value::CreateStringValue(ipconfig_path
));
406 void ShillServiceClientStub::RemoveService(const std::string
& service_path
) {
407 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
408 RemoveManagerService(service_path
);
410 stub_services_
.RemoveWithoutPathExpansion(service_path
, NULL
);
413 bool ShillServiceClientStub::SetServiceProperty(const std::string
& service_path
,
414 const std::string
& property
,
415 const base::Value
& value
) {
416 base::DictionaryValue
* dict
= NULL
;
417 if (!stub_services_
.GetDictionaryWithoutPathExpansion(service_path
, &dict
))
420 VLOG(1) << "Service.SetProperty: " << property
<< " = " << value
421 << " For: " << service_path
;
423 base::DictionaryValue new_properties
;
424 std::string changed_property
;
425 bool case_sensitive
= true;
426 if (StartsWithASCII(property
, "Provider.", case_sensitive
) ||
427 StartsWithASCII(property
, "OpenVPN.", case_sensitive
) ||
428 StartsWithASCII(property
, "L2TPIPsec.", case_sensitive
)) {
429 // These properties are only nested within the Provider dictionary if read
431 base::DictionaryValue
* provider
= new base::DictionaryValue
;
432 provider
->SetWithoutPathExpansion(property
, value
.DeepCopy());
433 new_properties
.SetWithoutPathExpansion(flimflam::kProviderProperty
,
435 changed_property
= flimflam::kProviderProperty
;
437 new_properties
.SetWithoutPathExpansion(property
, value
.DeepCopy());
438 changed_property
= property
;
441 dict
->MergeDictionary(&new_properties
);
443 if (property
== flimflam::kStateProperty
) {
444 // When State changes the sort order of Services may change.
445 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
446 SortManagerServices();
449 base::MessageLoop::current()->PostTask(
451 base::Bind(&ShillServiceClientStub::NotifyObserversPropertyChanged
,
452 weak_ptr_factory_
.GetWeakPtr(),
453 dbus::ObjectPath(service_path
), changed_property
));
457 const base::DictionaryValue
* ShillServiceClientStub::GetServiceProperties(
458 const std::string
& service_path
) const {
459 const base::DictionaryValue
* properties
= NULL
;
460 stub_services_
.GetDictionaryWithoutPathExpansion(service_path
, &properties
);
464 void ShillServiceClientStub::ClearServices() {
465 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
466 ClearManagerServices();
468 stub_services_
.Clear();
471 void ShillServiceClientStub::AddDefaultServices() {
472 const bool add_to_visible
= true;
473 const bool add_to_watchlist
= true;
475 if (!CommandLine::ForCurrentProcess()->HasSwitch(
476 chromeos::switches::kDisableStubEthernet
)) {
477 AddService("eth1", "eth1",
478 flimflam::kTypeEthernet
,
479 flimflam::kStateOnline
,
480 add_to_visible
, add_to_watchlist
);
485 AddService("wifi1", "wifi1",
487 flimflam::kStateOnline
,
488 add_to_visible
, add_to_watchlist
);
489 SetServiceProperty("wifi1",
490 flimflam::kSecurityProperty
,
491 base::StringValue(flimflam::kSecurityWep
));
493 AddService("wifi2", "wifi2_PSK",
495 flimflam::kStateIdle
,
496 add_to_visible
, add_to_watchlist
);
497 SetServiceProperty("wifi2",
498 flimflam::kSecurityProperty
,
499 base::StringValue(flimflam::kSecurityPsk
));
500 base::FundamentalValue
strength_value(80);
501 SetServiceProperty("wifi2",
502 flimflam::kSignalStrengthProperty
,
505 if (CommandLine::ForCurrentProcess()->HasSwitch(
506 chromeos::switches::kEnableStubPortalledWifi
)) {
507 AddService(kStubPortalledWifiPath
, kStubPortalledWifiName
,
509 flimflam::kStatePortal
,
510 add_to_visible
, add_to_watchlist
);
511 SetServiceProperty(kStubPortalledWifiPath
,
512 flimflam::kSecurityProperty
,
513 base::StringValue(flimflam::kSecurityNone
));
518 AddService("wimax1", "wimax1",
519 flimflam::kTypeWimax
,
520 flimflam::kStateIdle
,
521 add_to_visible
, add_to_watchlist
);
522 SetServiceProperty("wimax1",
523 flimflam::kConnectableProperty
,
524 base::FundamentalValue(true));
528 AddService("cellular1", "cellular1",
529 flimflam::kTypeCellular
,
530 flimflam::kStateIdle
,
531 add_to_visible
, add_to_watchlist
);
532 base::StringValue
technology_value(flimflam::kNetworkTechnologyGsm
);
533 SetServiceProperty("cellular1",
534 flimflam::kNetworkTechnologyProperty
,
536 SetServiceProperty("cellular1",
537 flimflam::kActivationStateProperty
,
538 base::StringValue(flimflam::kActivationStateNotActivated
));
539 SetServiceProperty("cellular1",
540 flimflam::kRoamingStateProperty
,
541 base::StringValue(flimflam::kRoamingStateHome
));
545 // Set the "Provider" dictionary properties. Note: when setting these in
546 // Shill, "Provider.Type", etc keys are used, but when reading the values
547 // "Provider" . "Type", etc keys are used. Here we are setting the values
548 // that will be read (by the UI, tests, etc).
549 base::DictionaryValue provider_properties
;
550 provider_properties
.SetString(flimflam::kTypeProperty
,
551 flimflam::kProviderOpenVpn
);
552 provider_properties
.SetString(flimflam::kHostProperty
, "vpn_host");
554 AddService("vpn1", "vpn1",
556 flimflam::kStateOnline
,
557 add_to_visible
, add_to_watchlist
);
558 SetServiceProperty("vpn1",
559 flimflam::kProviderProperty
,
560 provider_properties
);
562 AddService("vpn2", "vpn2",
564 flimflam::kStateOffline
,
565 add_to_visible
, add_to_watchlist
);
566 SetServiceProperty("vpn2",
567 flimflam::kProviderProperty
,
568 provider_properties
);
570 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface()->
571 AddService(ShillProfileClientStub::kSharedProfilePath
, "wifi2");
573 DBusThreadManager::Get()->GetShillManagerClient()->GetTestInterface()->
574 SortManagerServices();
577 void ShillServiceClientStub::NotifyObserversPropertyChanged(
578 const dbus::ObjectPath
& service_path
,
579 const std::string
& property
) {
580 base::DictionaryValue
* dict
= NULL
;
581 std::string path
= service_path
.value();
582 if (!stub_services_
.GetDictionaryWithoutPathExpansion(path
, &dict
)) {
583 LOG(ERROR
) << "Notify for unknown service: " << path
;
586 base::Value
* value
= NULL
;
587 if (!dict
->GetWithoutPathExpansion(property
, &value
)) {
588 LOG(ERROR
) << "Notify for unknown property: "
589 << path
<< " : " << property
;
592 FOR_EACH_OBSERVER(ShillPropertyChangedObserver
,
593 GetObserverList(service_path
),
594 OnPropertyChanged(property
, *value
));
597 base::DictionaryValue
* ShillServiceClientStub::GetModifiableServiceProperties(
598 const std::string
& service_path
) {
599 base::DictionaryValue
* properties
= NULL
;
600 if (!stub_services_
.GetDictionaryWithoutPathExpansion(
601 service_path
, &properties
)) {
602 properties
= new base::DictionaryValue
;
603 stub_services_
.Set(service_path
, properties
);
608 ShillServiceClientStub::PropertyObserverList
&
609 ShillServiceClientStub::GetObserverList(const dbus::ObjectPath
& device_path
) {
610 std::map
<dbus::ObjectPath
, PropertyObserverList
*>::iterator iter
=
611 observer_list_
.find(device_path
);
612 if (iter
!= observer_list_
.end())
613 return *(iter
->second
);
614 PropertyObserverList
* observer_list
= new PropertyObserverList();
615 observer_list_
[device_path
] = observer_list
;
616 return *observer_list
;
619 void ShillServiceClientStub::SetOtherServicesOffline(
620 const std::string
& service_path
) {
621 const base::DictionaryValue
* service_properties
= GetServiceProperties(
623 if (!service_properties
) {
624 LOG(ERROR
) << "Missing service: " << service_path
;
627 std::string service_type
;
628 service_properties
->GetString(flimflam::kTypeProperty
, &service_type
);
629 // Set all other services of the same type to offline (Idle).
630 for (base::DictionaryValue::Iterator
iter(stub_services_
);
631 !iter
.IsAtEnd(); iter
.Advance()) {
632 std::string path
= iter
.key();
633 if (path
== service_path
)
635 base::DictionaryValue
* properties
;
636 if (!stub_services_
.GetDictionaryWithoutPathExpansion(path
, &properties
))
640 properties
->GetString(flimflam::kTypeProperty
, &type
);
641 if (type
!= service_type
)
643 properties
->SetWithoutPathExpansion(
644 flimflam::kStateProperty
,
645 base::Value::CreateStringValue(flimflam::kStateIdle
));
649 void ShillServiceClientStub::SetCellularActivated(
650 const dbus::ObjectPath
& service_path
,
651 const ErrorCallback
& error_callback
) {
652 SetProperty(service_path
,
653 flimflam::kActivationStateProperty
,
654 base::StringValue(flimflam::kActivationStateActivated
),
655 base::Bind(&base::DoNothing
),
657 SetProperty(service_path
,
658 flimflam::kConnectableProperty
,
659 base::FundamentalValue(true),
660 base::Bind(&base::DoNothing
),
664 } // namespace chromeos