wifi_manager_nonchromeos.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
1// Copyright 2014 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 "chrome/browser/local_discovery/wifi/wifi_manager_nonchromeos.h"
6
7#include "base/cancelable_callback.h"
8#include "base/threading/sequenced_worker_pool.h"
9#include "base/threading/thread.h"
10#include "components/onc/onc_constants.h"
11#include "components/wifi/wifi_service.h"
12#include "content/public/browser/browser_thread.h"
13#include "net/base/network_change_notifier.h"
14
15using ::wifi::WiFiService;
16
17namespace local_discovery {
18
19namespace wifi {
20
21namespace {
22
23const int kConnectionTimeoutSeconds = 10;
24
25scoped_ptr<base::DictionaryValue> MakeProperties(const std::string& ssid,
26                                                 const std::string& password) {
27  scoped_ptr<base::DictionaryValue> properties(new base::DictionaryValue);
28
29  properties->SetString(onc::network_config::kType, onc::network_type::kWiFi);
30  base::DictionaryValue* wifi = new base::DictionaryValue;
31  properties->Set(onc::network_config::kWiFi, wifi);
32
33  wifi->SetString(onc::wifi::kSSID, ssid);
34  if (!password.empty()) {
35    wifi->SetString(onc::wifi::kPassphrase, password);
36    // TODO(noamsml): Allow choosing security mechanism in a more fine-grained
37    // manner.
38    wifi->SetString(onc::wifi::kSecurity, onc::wifi::kWPA2_PSK);
39  } else {
40    wifi->SetString(onc::wifi::kSecurity, onc::wifi::kNone);
41  }
42
43  return properties.Pass();
44}
45
46}  // namespace
47
48class WifiManagerNonChromeos::WifiServiceWrapper
49    : public net::NetworkChangeNotifier::NetworkChangeObserver {
50 public:
51  explicit WifiServiceWrapper(
52      base::WeakPtr<WifiManagerNonChromeos> wifi_manager);
53
54  virtual ~WifiServiceWrapper();
55
56  void Start();
57
58  void GetSSIDList(const WifiManager::SSIDListCallback& callback);
59
60  void ConfigureAndConnectPskNetwork(
61      const std::string& ssid,
62      const std::string& password,
63      const WifiManager::SuccessCallback& callback);
64
65  base::WeakPtr<WifiManagerNonChromeos::WifiServiceWrapper> AsWeakPtr();
66
67  void RequestScan();
68
69  void ConnectToNetworkByID(const std::string& network_guid,
70                            const WifiManager::SuccessCallback& callback);
71
72  void RequestNetworkCredentials(
73      const std::string& network_guid,
74      const WifiManager::CredentialsCallback& callback);
75
76 private:
77  // net::NetworkChangeNotifier::NetworkChangeObserver implementation.
78  virtual void OnNetworkChanged(
79      net::NetworkChangeNotifier::ConnectionType type) OVERRIDE;
80
81  void GetSSIDListInternal(NetworkPropertiesList* ssid_list);
82
83  void OnNetworkListChangedEvent(const std::vector<std::string>& network_guids);
84
85  void OnNetworksChangedEvent(const std::vector<std::string>& network_guids);
86
87  std::string GetConnectedGUID();
88
89  bool IsConnected(const std::string& network_guid);
90
91  void OnConnectToNetworkTimeout();
92
93  void PostClosure(const base::Closure& closure);
94
95  bool FindAndConfigureNetwork(const std::string& ssid,
96                               const std::string& password,
97                               std::string* network_guid);
98
99  scoped_ptr<WiFiService> wifi_service_;
100
101  base::WeakPtr<WifiManagerNonChromeos> wifi_manager_;
102
103  WifiManager::SuccessCallback connect_success_callback_;
104  base::CancelableClosure connect_failure_callback_;
105
106  // SSID of previously connected network.
107  std::string connected_network_guid_;
108
109  // SSID of network we are connecting to.
110  std::string connecting_network_guid_;
111
112  scoped_refptr<base::MessageLoopProxy> callback_runner_;
113
114  base::WeakPtrFactory<WifiServiceWrapper> weak_factory_;
115
116  DISALLOW_COPY_AND_ASSIGN(WifiServiceWrapper);
117};
118
119WifiManagerNonChromeos::WifiServiceWrapper::WifiServiceWrapper(
120    base::WeakPtr<WifiManagerNonChromeos> wifi_manager)
121    : wifi_manager_(wifi_manager),
122      callback_runner_(base::MessageLoopProxy::current()),
123      weak_factory_(this) {
124}
125
126WifiManagerNonChromeos::WifiServiceWrapper::~WifiServiceWrapper() {
127  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
128}
129
130void WifiManagerNonChromeos::WifiServiceWrapper::Start() {
131  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
132  wifi_service_.reset(WiFiService::Create());
133
134  wifi_service_->Initialize(base::MessageLoopProxy::current());
135
136  wifi_service_->SetEventObservers(
137      base::MessageLoopProxy::current(),
138      base::Bind(&WifiServiceWrapper::OnNetworksChangedEvent, AsWeakPtr()),
139      base::Bind(&WifiServiceWrapper::OnNetworkListChangedEvent, AsWeakPtr()));
140
141  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
142}
143
144void WifiManagerNonChromeos::WifiServiceWrapper::GetSSIDList(
145    const WifiManager::SSIDListCallback& callback) {
146  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
147
148  scoped_ptr<NetworkPropertiesList> ssid_list(new NetworkPropertiesList);
149  GetSSIDListInternal(ssid_list.get());
150
151  callback_runner_->PostTask(
152      FROM_HERE,
153      base::Bind(&WifiManagerNonChromeos::PostSSIDListCallback,
154                 wifi_manager_,
155                 callback,
156                 base::Passed(&ssid_list)));
157}
158
159void WifiManagerNonChromeos::WifiServiceWrapper::ConfigureAndConnectPskNetwork(
160    const std::string& ssid,
161    const std::string& password,
162    const WifiManager::SuccessCallback& callback) {
163  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
164  scoped_ptr<base::DictionaryValue> properties = MakeProperties(ssid, password);
165
166  std::string network_guid;
167  std::string error_string;
168  // Will fail without changing system state if network already exists.
169  wifi_service_->CreateNetwork(
170      false, properties.Pass(), &network_guid, &error_string);
171
172  if (error_string.empty()) {
173    ConnectToNetworkByID(network_guid, callback);
174    return;
175  }
176
177  // If we cannot create the network, attempt to configure and connect to an
178  // existing network.
179  if (FindAndConfigureNetwork(ssid, password, &network_guid)) {
180    ConnectToNetworkByID(network_guid, callback);
181  } else {
182    if (!callback.is_null())
183      PostClosure(base::Bind(callback, false /* success */));
184  }
185}
186
187void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworkListChangedEvent(
188    const std::vector<std::string>& network_guids) {
189  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
190  scoped_ptr<NetworkPropertiesList> ssid_list(new NetworkPropertiesList);
191  GetSSIDListInternal(ssid_list.get());
192  callback_runner_->PostTask(
193      FROM_HERE,
194      base::Bind(&WifiManagerNonChromeos::OnNetworkListChanged,
195                 wifi_manager_,
196                 base::Passed(&ssid_list)));
197}
198
199void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworksChangedEvent(
200    const std::vector<std::string>& network_guids) {
201  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
202  if (connecting_network_guid_.empty() ||
203      !IsConnected(connecting_network_guid_)) {
204    return;
205  }
206
207  connecting_network_guid_.clear();
208  connect_failure_callback_.Cancel();
209
210  if (!connect_success_callback_.is_null())
211    PostClosure(base::Bind(connect_success_callback_, true));
212
213  connect_success_callback_.Reset();
214}
215
216base::WeakPtr<WifiManagerNonChromeos::WifiServiceWrapper>
217WifiManagerNonChromeos::WifiServiceWrapper::AsWeakPtr() {
218  return weak_factory_.GetWeakPtr();
219}
220
221void WifiManagerNonChromeos::WifiServiceWrapper::RequestScan() {
222  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
223  wifi_service_->RequestNetworkScan();
224}
225
226void WifiManagerNonChromeos::WifiServiceWrapper::ConnectToNetworkByID(
227    const std::string& network_guid,
228    const WifiManager::SuccessCallback& callback) {
229  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
230
231  std::string connected_network_id = GetConnectedGUID();
232  std::string error_string;
233  wifi_service_->StartConnect(network_guid, &error_string);
234
235  if (!error_string.empty()) {
236    LOG(ERROR) << "Could not connect to network by ID: " << error_string;
237    PostClosure(base::Bind(callback, false /* success */));
238    wifi_service_->StartConnect(connected_network_id, &error_string);
239    return;
240  }
241
242  if (IsConnected(network_guid)) {
243    if (!callback.is_null())
244      PostClosure(base::Bind(callback, true /* success */));
245    return;
246  }
247
248  connect_success_callback_ = callback;
249  connecting_network_guid_ = network_guid;
250  connected_network_guid_ = connected_network_id;
251
252  connect_failure_callback_.Reset(base::Bind(
253      &WifiServiceWrapper::OnConnectToNetworkTimeout, base::Unretained(this)));
254
255  base::MessageLoop::current()->PostDelayedTask(
256      FROM_HERE,
257      connect_failure_callback_.callback(),
258      base::TimeDelta::FromSeconds(kConnectionTimeoutSeconds));
259}
260
261void WifiManagerNonChromeos::WifiServiceWrapper::OnConnectToNetworkTimeout() {
262  bool connected = IsConnected(connecting_network_guid_);
263  std::string error_string;
264
265  WifiManager::SuccessCallback connect_success_callback =
266      connect_success_callback_;
267
268  connect_success_callback_.Reset();
269
270  connecting_network_guid_.clear();
271
272  // If we did not connect, return to the network the user was originally
273  // connected to.
274  if (!connected)
275    wifi_service_->StartConnect(connected_network_guid_, &error_string);
276
277  PostClosure(base::Bind(connect_success_callback, connected /* success */));
278}
279
280void WifiManagerNonChromeos::WifiServiceWrapper::RequestNetworkCredentials(
281    const std::string& network_guid,
282    const WifiManager::CredentialsCallback& callback) {
283  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
284
285  bool success = true;
286  std::string ssid;
287  std::string key;
288
289#if defined(OS_WIN)
290  NOTIMPLEMENTED();
291  success = false;
292#else
293  base::DictionaryValue properties;
294  std::string error_string;
295  wifi_service_->GetProperties(network_guid, &properties, &error_string);
296
297  if (!error_string.empty()) {
298    LOG(ERROR) << "Could not get network properties: " << error_string;
299    success = false;
300  }
301
302  if (!properties.GetString(onc::network_config::kName, &ssid)) {
303    LOG(ERROR) << "Could not get network SSID";
304    success = false;
305  }
306
307  if (success) {
308    wifi_service_->GetKeyFromSystem(network_guid, &key, &error_string);
309
310    if (!error_string.empty()) {
311      LOG(ERROR) << "Could not get key from system: " << error_string;
312      success = false;
313    }
314  }
315#endif  // OS_WIN
316  PostClosure(base::Bind(callback, success, ssid, key));
317}
318
319void WifiManagerNonChromeos::WifiServiceWrapper::OnNetworkChanged(
320    net::NetworkChangeNotifier::ConnectionType type) {
321  wifi_service_->RequestConnectedNetworkUpdate();
322}
323
324void WifiManagerNonChromeos::WifiServiceWrapper::GetSSIDListInternal(
325    NetworkPropertiesList* ssid_list) {
326  base::ListValue visible_networks;
327
328  wifi_service_->GetVisibleNetworks(
329      onc::network_type::kWiFi, &visible_networks, true);
330
331  for (size_t i = 0; i < visible_networks.GetSize(); i++) {
332    const base::DictionaryValue* network_value = NULL;
333    NetworkProperties network_properties;
334
335    if (!visible_networks.GetDictionary(i, &network_value)) {
336      NOTREACHED();
337    }
338
339    network_properties.UpdateFromValue(*network_value);
340
341    ssid_list->push_back(network_properties);
342  }
343}
344
345std::string WifiManagerNonChromeos::WifiServiceWrapper::GetConnectedGUID() {
346  NetworkPropertiesList ssid_list;
347  GetSSIDListInternal(&ssid_list);
348
349  for (NetworkPropertiesList::const_iterator it = ssid_list.begin();
350       it != ssid_list.end();
351       ++it) {
352    if (it->connection_state == onc::connection_state::kConnected)
353      return it->guid;
354  }
355
356  return "";
357}
358
359bool WifiManagerNonChromeos::WifiServiceWrapper::IsConnected(
360    const std::string& network_guid) {
361  NetworkPropertiesList ssid_list;
362  GetSSIDListInternal(&ssid_list);
363
364  for (NetworkPropertiesList::const_iterator it = ssid_list.begin();
365       it != ssid_list.end();
366       ++it) {
367    if (it->connection_state == onc::connection_state::kConnected &&
368        it->guid == network_guid)
369      return true;
370  }
371
372  return false;
373}
374
375bool WifiManagerNonChromeos::WifiServiceWrapper::FindAndConfigureNetwork(
376    const std::string& ssid,
377    const std::string& password,
378    std::string* network_guid) {
379  std::string provisional_network_guid;
380  NetworkPropertiesList network_property_list;
381  GetSSIDListInternal(&network_property_list);
382
383  for (size_t i = 0; i < network_property_list.size(); i++) {
384    // TODO(noamsml): Handle case where there are multiple networks with the
385    // same SSID but different security.
386    if (network_property_list[i].ssid == ssid) {
387      provisional_network_guid = network_property_list[i].guid;
388      break;
389    }
390  }
391
392  if (provisional_network_guid.empty())
393    return false;
394
395  scoped_ptr<base::DictionaryValue> properties = MakeProperties(ssid, password);
396
397  std::string error_string;
398  wifi_service_->SetProperties(
399      provisional_network_guid, properties.Pass(), &error_string);
400
401  if (!error_string.empty()) {
402    LOG(ERROR) << "Could not set properties on network: " << error_string;
403    return false;
404  }
405
406  *network_guid = provisional_network_guid;
407  return true;
408}
409
410void WifiManagerNonChromeos::WifiServiceWrapper::PostClosure(
411    const base::Closure& closure) {
412  callback_runner_->PostTask(
413      FROM_HERE,
414      base::Bind(&WifiManagerNonChromeos::PostClosure, wifi_manager_, closure));
415}
416
417scoped_ptr<WifiManager> WifiManager::CreateDefault() {
418  return scoped_ptr<WifiManager>(new WifiManagerNonChromeos());
419}
420
421WifiManagerNonChromeos::WifiManagerNonChromeos()
422    : wifi_wrapper_(NULL), weak_factory_(this) {
423}
424
425WifiManagerNonChromeos::~WifiManagerNonChromeos() {
426  if (wifi_wrapper_) {
427    content::BrowserThread::DeleteSoon(
428        content::BrowserThread::FILE, FROM_HERE, wifi_wrapper_);
429  }
430}
431
432void WifiManagerNonChromeos::Start() {
433  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
434  task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
435      content::BrowserThread::FILE);
436
437  // Allocated on UI thread, but all initialization is done on file
438  // thread. Destroyed on file thread, which should be safe since all of the
439  // thread-unsafe members are created on the file thread.
440  wifi_wrapper_ = new WifiServiceWrapper(weak_factory_.GetWeakPtr());
441
442  task_runner_->PostTask(
443      FROM_HERE,
444      base::Bind(&WifiServiceWrapper::Start, wifi_wrapper_->AsWeakPtr()));
445}
446
447void WifiManagerNonChromeos::GetSSIDList(const SSIDListCallback& callback) {
448  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
449  task_runner_->PostTask(FROM_HERE,
450                         base::Bind(&WifiServiceWrapper::GetSSIDList,
451                                    wifi_wrapper_->AsWeakPtr(),
452                                    callback));
453}
454
455void WifiManagerNonChromeos::RequestScan() {
456  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
457  task_runner_->PostTask(
458      FROM_HERE,
459      base::Bind(&WifiServiceWrapper::RequestScan, wifi_wrapper_->AsWeakPtr()));
460}
461
462void WifiManagerNonChromeos::OnNetworkListChanged(
463    scoped_ptr<NetworkPropertiesList> ssid_list) {
464  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
465  FOR_EACH_OBSERVER(NetworkListObserver,
466                    network_list_observers_,
467                    OnNetworkListChanged(*ssid_list));
468}
469
470void WifiManagerNonChromeos::PostClosure(const base::Closure& callback) {
471  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
472  callback.Run();
473}
474
475void WifiManagerNonChromeos::PostSSIDListCallback(
476    const SSIDListCallback& callback,
477    scoped_ptr<NetworkPropertiesList> ssid_list) {
478  callback.Run(*ssid_list);
479}
480
481void WifiManagerNonChromeos::ConfigureAndConnectNetwork(
482    const std::string& ssid,
483    const WifiCredentials& credentials,
484    const SuccessCallback& callback) {
485  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
486  task_runner_->PostTask(
487      FROM_HERE,
488      base::Bind(&WifiServiceWrapper::ConfigureAndConnectPskNetwork,
489                 wifi_wrapper_->AsWeakPtr(),
490                 ssid,
491                 credentials.psk,
492                 callback));
493}
494
495void WifiManagerNonChromeos::ConnectToNetworkByID(
496    const std::string& internal_id,
497    const SuccessCallback& callback) {
498  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
499  task_runner_->PostTask(FROM_HERE,
500                         base::Bind(&WifiServiceWrapper::ConnectToNetworkByID,
501                                    wifi_wrapper_->AsWeakPtr(),
502                                    internal_id,
503                                    callback));
504}
505
506void WifiManagerNonChromeos::RequestNetworkCredentials(
507    const std::string& internal_id,
508    const CredentialsCallback& callback) {
509  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
510  task_runner_->PostTask(
511      FROM_HERE,
512      base::Bind(&WifiServiceWrapper::RequestNetworkCredentials,
513                 wifi_wrapper_->AsWeakPtr(),
514                 internal_id,
515                 callback));
516}
517
518void WifiManagerNonChromeos::AddNetworkListObserver(
519    NetworkListObserver* observer) {
520  network_list_observers_.AddObserver(observer);
521}
522
523void WifiManagerNonChromeos::RemoveNetworkListObserver(
524    NetworkListObserver* observer) {
525  network_list_observers_.RemoveObserver(observer);
526}
527
528}  // namespace wifi
529
530}  // namespace local_discovery
531