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 "components/wifi/wifi_service.h" 6 7 #import <netinet/in.h> 8 #import <CoreWLAN/CoreWLAN.h> 9 #import <SystemConfiguration/SystemConfiguration.h> 10 11 #include "base/bind.h" 12 #include "base/mac/foundation_util.h" 13 #include "base/mac/scoped_cftyperef.h" 14 #include "base/mac/scoped_nsobject.h" 15 #include "base/mac/sdk_forward_declarations.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/strings/sys_string_conversions.h" 18 #include "components/onc/onc_constants.h" 19 #include "components/wifi/network_properties.h" 20 21 namespace wifi { 22 23 // Implementation of WiFiService for Mac OS X. 24 class WiFiServiceMac : public WiFiService { 25 public: 26 WiFiServiceMac(); 27 virtual ~WiFiServiceMac(); 28 29 // WiFiService interface implementation. 30 virtual void Initialize( 31 scoped_refptr<base::SequencedTaskRunner> task_runner) OVERRIDE; 32 33 virtual void UnInitialize() OVERRIDE; 34 35 virtual void GetProperties(const std::string& network_guid, 36 base::DictionaryValue* properties, 37 std::string* error) OVERRIDE; 38 39 virtual void GetManagedProperties(const std::string& network_guid, 40 base::DictionaryValue* managed_properties, 41 std::string* error) OVERRIDE; 42 43 virtual void GetState(const std::string& network_guid, 44 base::DictionaryValue* properties, 45 std::string* error) OVERRIDE; 46 47 virtual void SetProperties(const std::string& network_guid, 48 scoped_ptr<base::DictionaryValue> properties, 49 std::string* error) OVERRIDE; 50 51 virtual void CreateNetwork(bool shared, 52 scoped_ptr<base::DictionaryValue> properties, 53 std::string* network_guid, 54 std::string* error) OVERRIDE; 55 56 virtual void GetVisibleNetworks(const std::string& network_type, 57 base::ListValue* network_list, 58 bool include_details) OVERRIDE; 59 60 virtual void RequestNetworkScan() OVERRIDE; 61 62 virtual void StartConnect(const std::string& network_guid, 63 std::string* error) OVERRIDE; 64 65 virtual void StartDisconnect(const std::string& network_guid, 66 std::string* error) OVERRIDE; 67 68 virtual void GetKeyFromSystem(const std::string& network_guid, 69 std::string* key_data, 70 std::string* error) OVERRIDE; 71 72 virtual void SetEventObservers( 73 scoped_refptr<base::MessageLoopProxy> message_loop_proxy, 74 const NetworkGuidListCallback& networks_changed_observer, 75 const NetworkGuidListCallback& network_list_changed_observer) OVERRIDE; 76 77 virtual void RequestConnectedNetworkUpdate() OVERRIDE; 78 79 private: 80 // Checks |ns_error| and if is not |nil|, then stores |error_name| 81 // into |error|. 82 bool CheckError(NSError* ns_error, 83 const char* error_name, 84 std::string* error) const; 85 86 // Gets |ssid| from unique |network_guid|. 87 NSString* SSIDFromGUID(const std::string& network_guid) const { 88 return base::SysUTF8ToNSString(network_guid); 89 } 90 91 // Gets unique |network_guid| string based on |ssid|. 92 std::string GUIDFromSSID(NSString* ssid) const { 93 return base::SysNSStringToUTF8(ssid); 94 } 95 96 // Populates |properties| from |network|. 97 void NetworkPropertiesFromCWNetwork(const CWNetwork* network, 98 NetworkProperties* properties) const; 99 100 // Converts |CWSecurityMode| into onc::wifi::k{WPA|WEP}* security constant. 101 std::string SecurityFromCWSecurityMode(CWSecurityMode security) const; 102 103 // Returns onc::wifi::k{WPA|WEP}* security constant supported by the 104 // |CWNetwork|. 105 std::string SecurityFromCWNetwork(const CWNetwork* network) const; 106 107 // Converts |CWChannelBand| into Frequency constant. 108 Frequency FrequencyFromCWChannelBand(CWChannelBand band) const; 109 110 // Gets current |onc::connection_state| for given |network_guid|. 111 std::string GetNetworkConnectionState(const std::string& network_guid) const; 112 113 // Updates |networks_| with the list of visible wireless networks. 114 void UpdateNetworks(); 115 116 // Find network by |network_guid| and return iterator to its entry in 117 // |networks_|. 118 NetworkList::iterator FindNetwork(const std::string& network_guid); 119 120 // Handles notification from |wlan_observer_|. 121 void OnWlanObserverNotification(); 122 123 // Notifies |network_list_changed_observer_| that list of visible networks has 124 // changed to |networks|. 125 void NotifyNetworkListChanged(const NetworkList& networks); 126 127 // Notifies |networks_changed_observer_| that network |network_guid| 128 // connection state has changed. 129 void NotifyNetworkChanged(const std::string& network_guid); 130 131 // Default interface. 132 base::scoped_nsobject<CWInterface> interface_; 133 // WLAN Notifications observer. |this| doesn't own this reference. 134 id wlan_observer_; 135 136 // Observer to get notified when network(s) have changed (e.g. connect). 137 NetworkGuidListCallback networks_changed_observer_; 138 // Observer to get notified when network list has changed. 139 NetworkGuidListCallback network_list_changed_observer_; 140 // MessageLoopProxy to which events should be posted. 141 scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; 142 // Task runner for worker tasks. 143 scoped_refptr<base::SequencedTaskRunner> task_runner_; 144 // Cached list of visible networks. Updated by |UpdateNetworks|. 145 NetworkList networks_; 146 // Guid of last known connected network. 147 std::string connected_network_guid_; 148 // Temporary storage of network properties indexed by |network_guid|. 149 base::DictionaryValue network_properties_; 150 151 DISALLOW_COPY_AND_ASSIGN(WiFiServiceMac); 152 }; 153 154 WiFiServiceMac::WiFiServiceMac() : wlan_observer_(nil) { 155 } 156 157 WiFiServiceMac::~WiFiServiceMac() { 158 } 159 160 void WiFiServiceMac::Initialize( 161 scoped_refptr<base::SequencedTaskRunner> task_runner) { 162 task_runner_.swap(task_runner); 163 interface_.reset([[CWInterface interface] retain]); 164 if (!interface_) { 165 DVLOG(1) << "Failed to initialize default interface."; 166 return; 167 } 168 169 if (![interface_ 170 respondsToSelector:@selector(associateToNetwork:password:error:)]) { 171 DVLOG(1) << "CWInterface does not support associateToNetwork."; 172 interface_.reset(); 173 return; 174 } 175 } 176 177 void WiFiServiceMac::UnInitialize() { 178 if (wlan_observer_) 179 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_]; 180 interface_.reset(); 181 } 182 183 void WiFiServiceMac::GetProperties(const std::string& network_guid, 184 base::DictionaryValue* properties, 185 std::string* error) { 186 NetworkList::iterator it = FindNetwork(network_guid); 187 if (it == networks_.end()) { 188 DVLOG(1) << "Network not found:" << network_guid; 189 *error = kErrorNotFound; 190 return; 191 } 192 193 it->connection_state = GetNetworkConnectionState(network_guid); 194 scoped_ptr<base::DictionaryValue> network(it->ToValue(false)); 195 properties->Swap(network.get()); 196 DVLOG(1) << *properties; 197 } 198 199 void WiFiServiceMac::GetManagedProperties( 200 const std::string& network_guid, 201 base::DictionaryValue* managed_properties, 202 std::string* error) { 203 *error = kErrorNotImplemented; 204 } 205 206 void WiFiServiceMac::GetState(const std::string& network_guid, 207 base::DictionaryValue* properties, 208 std::string* error) { 209 *error = kErrorNotImplemented; 210 } 211 212 void WiFiServiceMac::SetProperties( 213 const std::string& network_guid, 214 scoped_ptr<base::DictionaryValue> properties, 215 std::string* error) { 216 base::DictionaryValue* existing_properties; 217 // If the network properties already exist, don't override previously set 218 // properties, unless they are set in |properties|. 219 if (network_properties_.GetDictionaryWithoutPathExpansion( 220 network_guid, &existing_properties)) { 221 existing_properties->MergeDictionary(properties.get()); 222 } else { 223 network_properties_.SetWithoutPathExpansion(network_guid, 224 properties.release()); 225 } 226 } 227 228 void WiFiServiceMac::CreateNetwork( 229 bool shared, 230 scoped_ptr<base::DictionaryValue> properties, 231 std::string* network_guid, 232 std::string* error) { 233 NetworkProperties network_properties; 234 if (!network_properties.UpdateFromValue(*properties)) { 235 *error = kErrorInvalidData; 236 return; 237 } 238 239 std::string guid = network_properties.ssid; 240 if (FindNetwork(guid) != networks_.end()) { 241 *error = kErrorInvalidData; 242 return; 243 } 244 network_properties_.SetWithoutPathExpansion(guid, 245 properties.release()); 246 *network_guid = guid; 247 } 248 249 void WiFiServiceMac::GetVisibleNetworks(const std::string& network_type, 250 base::ListValue* network_list, 251 bool include_details) { 252 if (!network_type.empty() && 253 network_type != onc::network_type::kAllTypes && 254 network_type != onc::network_type::kWiFi) { 255 return; 256 } 257 258 if (networks_.empty()) 259 UpdateNetworks(); 260 261 for (NetworkList::const_iterator it = networks_.begin(); 262 it != networks_.end(); 263 ++it) { 264 scoped_ptr<base::DictionaryValue> network(it->ToValue(!include_details)); 265 network_list->Append(network.release()); 266 } 267 } 268 269 void WiFiServiceMac::RequestNetworkScan() { 270 DVLOG(1) << "*** RequestNetworkScan"; 271 UpdateNetworks(); 272 } 273 274 void WiFiServiceMac::StartConnect(const std::string& network_guid, 275 std::string* error) { 276 NSError* ns_error = nil; 277 278 DVLOG(1) << "*** StartConnect: " << network_guid; 279 // Remember previously connected network. 280 std::string connected_network_guid = GUIDFromSSID([interface_ ssid]); 281 // Check whether desired network is already connected. 282 if (network_guid == connected_network_guid) 283 return; 284 285 NSSet* networks = [interface_ 286 scanForNetworksWithName:SSIDFromGUID(network_guid) 287 error:&ns_error]; 288 289 if (CheckError(ns_error, kErrorScanForNetworksWithName, error)) 290 return; 291 292 CWNetwork* network = [networks anyObject]; 293 if (network == nil) { 294 // System can't find the network, remove it from the |networks_| and notify 295 // observers. 296 NetworkList::iterator it = FindNetwork(connected_network_guid); 297 if (it != networks_.end()) { 298 networks_.erase(it); 299 // Notify observers that list has changed. 300 NotifyNetworkListChanged(networks_); 301 } 302 303 *error = kErrorNotFound; 304 return; 305 } 306 307 // Check whether WiFi Password is set in |network_properties_|. 308 base::DictionaryValue* properties; 309 base::DictionaryValue* wifi; 310 std::string passphrase; 311 NSString* ns_password = nil; 312 if (network_properties_.GetDictionaryWithoutPathExpansion(network_guid, 313 &properties) && 314 properties->GetDictionary(onc::network_type::kWiFi, &wifi) && 315 wifi->GetString(onc::wifi::kPassphrase, &passphrase)) { 316 ns_password = base::SysUTF8ToNSString(passphrase); 317 } 318 319 // Number of attempts to associate to network. 320 static const int kMaxAssociationAttempts = 3; 321 // Try to associate to network several times if timeout or PMK error occurs. 322 for (int i = 0; i < kMaxAssociationAttempts; ++i) { 323 // Nil out the PMK to prevent stale data from causing invalid PMK error 324 // (CoreWLANTypes -3924). 325 [interface_ setPairwiseMasterKey:nil error:&ns_error]; 326 if (![interface_ associateToNetwork:network 327 password:ns_password 328 error:&ns_error]) { 329 NSInteger error_code = [ns_error code]; 330 if (error_code != kCWTimeoutErr && error_code != kCWInvalidPMKErr) { 331 break; 332 } 333 } 334 } 335 CheckError(ns_error, kErrorAssociateToNetwork, error); 336 } 337 338 void WiFiServiceMac::StartDisconnect(const std::string& network_guid, 339 std::string* error) { 340 DVLOG(1) << "*** StartDisconnect: " << network_guid; 341 342 if (network_guid == GUIDFromSSID([interface_ ssid])) { 343 // Power-cycle the interface to disconnect from current network and connect 344 // to default network. 345 NSError* ns_error = nil; 346 [interface_ setPower:NO error:&ns_error]; 347 CheckError(ns_error, kErrorAssociateToNetwork, error); 348 [interface_ setPower:YES error:&ns_error]; 349 CheckError(ns_error, kErrorAssociateToNetwork, error); 350 } else { 351 *error = kErrorNotConnected; 352 } 353 } 354 355 void WiFiServiceMac::GetKeyFromSystem(const std::string& network_guid, 356 std::string* key_data, 357 std::string* error) { 358 static const char kAirPortServiceName[] = "AirPort"; 359 360 UInt32 password_length = 0; 361 void *password_data = NULL; 362 OSStatus status = SecKeychainFindGenericPassword(NULL, 363 strlen(kAirPortServiceName), 364 kAirPortServiceName, 365 network_guid.length(), 366 network_guid.c_str(), 367 &password_length, 368 &password_data, 369 NULL); 370 if (status != errSecSuccess) { 371 *error = kErrorNotFound; 372 return; 373 } 374 375 if (password_data) { 376 *key_data = std::string(reinterpret_cast<char*>(password_data), 377 password_length); 378 SecKeychainItemFreeContent(NULL, password_data); 379 } 380 } 381 382 void WiFiServiceMac::SetEventObservers( 383 scoped_refptr<base::MessageLoopProxy> message_loop_proxy, 384 const NetworkGuidListCallback& networks_changed_observer, 385 const NetworkGuidListCallback& network_list_changed_observer) { 386 message_loop_proxy_.swap(message_loop_proxy); 387 networks_changed_observer_ = networks_changed_observer; 388 network_list_changed_observer_ = network_list_changed_observer; 389 390 // Remove previous OS notifications observer. 391 if (wlan_observer_) { 392 [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_]; 393 wlan_observer_ = nil; 394 } 395 396 // Subscribe to OS notifications. 397 if (!networks_changed_observer_.is_null()) { 398 void (^ns_observer) (NSNotification* notification) = 399 ^(NSNotification* notification) { 400 DVLOG(1) << "Received CWSSIDDidChangeNotification"; 401 task_runner_->PostTask( 402 FROM_HERE, 403 base::Bind(&WiFiServiceMac::OnWlanObserverNotification, 404 base::Unretained(this))); 405 }; 406 407 wlan_observer_ = [[NSNotificationCenter defaultCenter] 408 addObserverForName:kCWSSIDDidChangeNotification 409 object:nil 410 queue:nil 411 usingBlock:ns_observer]; 412 } 413 } 414 415 void WiFiServiceMac::RequestConnectedNetworkUpdate() { 416 OnWlanObserverNotification(); 417 } 418 419 std::string WiFiServiceMac::GetNetworkConnectionState( 420 const std::string& network_guid) const { 421 if (network_guid != GUIDFromSSID([interface_ ssid])) 422 return onc::connection_state::kNotConnected; 423 424 // Check whether WiFi network is reachable. 425 struct sockaddr_in local_wifi_address; 426 bzero(&local_wifi_address, sizeof(local_wifi_address)); 427 local_wifi_address.sin_len = sizeof(local_wifi_address); 428 local_wifi_address.sin_family = AF_INET; 429 local_wifi_address.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 430 base::ScopedCFTypeRef<SCNetworkReachabilityRef> reachability( 431 SCNetworkReachabilityCreateWithAddress( 432 kCFAllocatorDefault, 433 reinterpret_cast<const struct sockaddr*>(&local_wifi_address))); 434 SCNetworkReachabilityFlags flags = 0u; 435 if (SCNetworkReachabilityGetFlags(reachability, &flags) && 436 (flags & kSCNetworkReachabilityFlagsReachable) && 437 (flags & kSCNetworkReachabilityFlagsIsDirect)) { 438 // Network is reachable, report is as |kConnected|. 439 return onc::connection_state::kConnected; 440 } 441 // Network is not reachable yet, so it must be |kConnecting|. 442 return onc::connection_state::kConnecting; 443 } 444 445 void WiFiServiceMac::UpdateNetworks() { 446 NSError* ns_error = nil; 447 NSSet* cw_networks = [interface_ scanForNetworksWithName:nil 448 error:&ns_error]; 449 if (ns_error != nil) 450 return; 451 452 std::string connected_bssid = base::SysNSStringToUTF8([interface_ bssid]); 453 std::map<std::string, NetworkProperties*> network_properties_map; 454 networks_.clear(); 455 456 // There is one |cw_network| per BSS in |cw_networks|, so go through the set 457 // and combine them, paying attention to supported frequencies. 458 for (CWNetwork* cw_network in cw_networks) { 459 std::string network_guid = GUIDFromSSID([cw_network ssid]); 460 bool update_all_properties = false; 461 462 if (network_properties_map.find(network_guid) == 463 network_properties_map.end()) { 464 networks_.push_back(NetworkProperties()); 465 network_properties_map[network_guid] = &networks_.back(); 466 update_all_properties = true; 467 } 468 // If current network is connected, use its properties for this network. 469 if (base::SysNSStringToUTF8([cw_network bssid]) == connected_bssid) 470 update_all_properties = true; 471 472 NetworkProperties* properties = network_properties_map.at(network_guid); 473 if (update_all_properties) { 474 NetworkPropertiesFromCWNetwork(cw_network, properties); 475 } else { 476 properties->frequency_set.insert(FrequencyFromCWChannelBand( 477 [[cw_network wlanChannel] channelBand])); 478 } 479 } 480 // Sort networks, so connected/connecting is up front. 481 networks_.sort(NetworkProperties::OrderByType); 482 // Notify observers that list has changed. 483 NotifyNetworkListChanged(networks_); 484 } 485 486 bool WiFiServiceMac::CheckError(NSError* ns_error, 487 const char* error_name, 488 std::string* error) const { 489 if (ns_error != nil) { 490 DLOG(ERROR) << "*** Error:" << error_name << ":" << [ns_error code]; 491 *error = error_name; 492 return true; 493 } 494 return false; 495 } 496 497 void WiFiServiceMac::NetworkPropertiesFromCWNetwork( 498 const CWNetwork* network, 499 NetworkProperties* properties) const { 500 std::string network_guid = GUIDFromSSID([network ssid]); 501 502 properties->connection_state = GetNetworkConnectionState(network_guid); 503 properties->ssid = base::SysNSStringToUTF8([network ssid]); 504 properties->name = properties->ssid; 505 properties->guid = network_guid; 506 properties->type = onc::network_type::kWiFi; 507 508 properties->bssid = base::SysNSStringToUTF8([network bssid]); 509 properties->frequency = FrequencyFromCWChannelBand( 510 static_cast<CWChannelBand>([[network wlanChannel] channelBand])); 511 properties->frequency_set.insert(properties->frequency); 512 513 // -[CWNetwork supportsSecurity:] is available from 10.7 SDK while 514 // -[CWNetwork securityMode] is deprecated and hidden as private since 515 // 10.9 SDK. The latter is kept for now to support running on 10.6. It 516 // should be removed when 10.6 support is dropped. 517 if ([network respondsToSelector:@selector(supportsSecurity:)]) { 518 properties->security = SecurityFromCWNetwork(network); 519 } else { 520 properties->security = SecurityFromCWSecurityMode( 521 static_cast<CWSecurityMode>([[network securityMode] intValue])); 522 } 523 524 // rssiValue property of CWNetwork is available from 10.7 SDK while 525 // -[CWNetwork rssi] is deprecated and hidden as private since 10.9 SDK. 526 // The latter is kept for now to support running on 10.6. It should be 527 // removed when 10.6 support is dropped. 528 if ([network respondsToSelector:@selector(rssiValue)]) 529 properties->signal_strength = [network rssiValue]; 530 else 531 properties->signal_strength = [[network rssi] intValue]; 532 } 533 534 std::string WiFiServiceMac::SecurityFromCWSecurityMode( 535 CWSecurityMode security) const { 536 switch (security) { 537 case kCWSecurityModeWPA_Enterprise: 538 case kCWSecurityModeWPA2_Enterprise: 539 return onc::wifi::kWPA_EAP; 540 case kCWSecurityModeWPA_PSK: 541 case kCWSecurityModeWPA2_PSK: 542 return onc::wifi::kWPA_PSK; 543 case kCWSecurityModeWEP: 544 return onc::wifi::kWEP_PSK; 545 case kCWSecurityModeOpen: 546 return onc::wifi::kSecurityNone; 547 // TODO(mef): Figure out correct mapping. 548 case kCWSecurityModeWPS: 549 case kCWSecurityModeDynamicWEP: 550 return onc::wifi::kWPA_EAP; 551 } 552 return onc::wifi::kWPA_EAP; 553 } 554 555 std::string WiFiServiceMac::SecurityFromCWNetwork( 556 const CWNetwork* network) const { 557 if ([network supportsSecurity:kCWSecurityWPAEnterprise] || 558 [network supportsSecurity:kCWSecurityWPA2Enterprise]) { 559 return onc::wifi::kWPA_EAP; 560 } 561 562 if ([network supportsSecurity:kCWSecurityWPAPersonal] || 563 [network supportsSecurity:kCWSecurityWPA2Personal]) { 564 return onc::wifi::kWPA_PSK; 565 } 566 567 if ([network supportsSecurity:kCWSecurityWEP]) 568 return onc::wifi::kWEP_PSK; 569 570 if ([network supportsSecurity:kCWSecurityNone]) 571 return onc::wifi::kSecurityNone; 572 573 // TODO(mef): Figure out correct mapping. 574 if ([network supportsSecurity:kCWSecurityDynamicWEP]) 575 return onc::wifi::kWPA_EAP; 576 577 return onc::wifi::kWPA_EAP; 578 } 579 580 Frequency WiFiServiceMac::FrequencyFromCWChannelBand(CWChannelBand band) const { 581 return band == kCWChannelBand2GHz ? kFrequency2400 : kFrequency5000; 582 } 583 584 NetworkList::iterator WiFiServiceMac::FindNetwork( 585 const std::string& network_guid) { 586 for (NetworkList::iterator it = networks_.begin(); 587 it != networks_.end(); 588 ++it) { 589 if (it->guid == network_guid) 590 return it; 591 } 592 return networks_.end(); 593 } 594 595 void WiFiServiceMac::OnWlanObserverNotification() { 596 std::string connected_network_guid = GUIDFromSSID([interface_ ssid]); 597 DVLOG(1) << " *** Got Notification: " << connected_network_guid; 598 // Connected network has changed, mark previous one disconnected. 599 if (connected_network_guid != connected_network_guid_) { 600 // Update connection_state of newly connected network. 601 NetworkList::iterator it = FindNetwork(connected_network_guid_); 602 if (it != networks_.end()) { 603 it->connection_state = onc::connection_state::kNotConnected; 604 NotifyNetworkChanged(connected_network_guid_); 605 } 606 connected_network_guid_ = connected_network_guid; 607 } 608 609 if (!connected_network_guid.empty()) { 610 // Update connection_state of newly connected network. 611 NetworkList::iterator it = FindNetwork(connected_network_guid); 612 if (it != networks_.end()) { 613 it->connection_state = GetNetworkConnectionState(connected_network_guid); 614 } else { 615 // Can't find |connected_network_guid| in |networks_|, try to update it. 616 UpdateNetworks(); 617 } 618 // Notify that network is connecting. 619 NotifyNetworkChanged(connected_network_guid); 620 // Further network change notification will be sent by detector. 621 } 622 } 623 624 void WiFiServiceMac::NotifyNetworkListChanged(const NetworkList& networks) { 625 if (network_list_changed_observer_.is_null()) 626 return; 627 628 NetworkGuidList current_networks; 629 for (NetworkList::const_iterator it = networks.begin(); 630 it != networks.end(); 631 ++it) { 632 current_networks.push_back(it->guid); 633 } 634 635 message_loop_proxy_->PostTask( 636 FROM_HERE, 637 base::Bind(network_list_changed_observer_, current_networks)); 638 } 639 640 void WiFiServiceMac::NotifyNetworkChanged(const std::string& network_guid) { 641 if (networks_changed_observer_.is_null()) 642 return; 643 644 DVLOG(1) << "NotifyNetworkChanged: " << network_guid; 645 NetworkGuidList changed_networks(1, network_guid); 646 message_loop_proxy_->PostTask( 647 FROM_HERE, 648 base::Bind(networks_changed_observer_, changed_networks)); 649 } 650 651 // static 652 WiFiService* WiFiService::Create() { return new WiFiServiceMac(); } 653 654 } // namespace wifi 655