• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "wificond/scanning/scanner_impl.h"
18 
19 #include <set>
20 #include <string>
21 #include <vector>
22 
23 #include <android-base/logging.h>
24 
25 #include "wificond/client_interface_impl.h"
26 #include "wificond/scanning/offload/offload_scan_manager.h"
27 #include "wificond/scanning/offload/offload_service_utils.h"
28 #include "wificond/scanning/scan_utils.h"
29 
30 using android::binder::Status;
31 using android::net::wifi::IPnoScanEvent;
32 using android::net::wifi::IScanEvent;
33 using android::net::wifi::IWifiScannerImpl;
34 using android::sp;
35 using com::android::server::wifi::wificond::NativeScanResult;
36 using com::android::server::wifi::wificond::PnoSettings;
37 using com::android::server::wifi::wificond::SingleScanSettings;
38 
39 using std::string;
40 using std::vector;
41 using std::weak_ptr;
42 using std::shared_ptr;
43 
44 using namespace std::placeholders;
45 
46 namespace {
47 using android::wificond::WiphyFeatures;
IsScanTypeSupported(int scan_type,const WiphyFeatures & wiphy_features)48 bool IsScanTypeSupported(int scan_type, const WiphyFeatures& wiphy_features) {
49   switch(scan_type) {
50     case IWifiScannerImpl::SCAN_TYPE_LOW_SPAN:
51       return wiphy_features.supports_low_span_oneshot_scan;
52     case IWifiScannerImpl::SCAN_TYPE_LOW_POWER:
53       return wiphy_features.supports_low_power_oneshot_scan;
54     case IWifiScannerImpl::SCAN_TYPE_HIGH_ACCURACY:
55       return wiphy_features.supports_high_accuracy_oneshot_scan;
56     default:
57       CHECK(0) << "Invalid scan type received: " << scan_type;
58   }
59   return {};
60 }
61 
62 constexpr const int kPercentNetworksWithFreq = 30;
63 constexpr const int kPnoScanDefaultFreqs[] = {2412, 2417, 2422, 2427, 2432, 2437, 2447, 2452,
64     2457, 2462, 5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805};
65 } // namespace
66 
67 namespace android {
68 namespace wificond {
69 
ScannerImpl(uint32_t interface_index,const ScanCapabilities & scan_capabilities,const WiphyFeatures & wiphy_features,ClientInterfaceImpl * client_interface,ScanUtils * scan_utils,weak_ptr<OffloadServiceUtils> offload_service_utils)70 ScannerImpl::ScannerImpl(uint32_t interface_index,
71                          const ScanCapabilities& scan_capabilities,
72                          const WiphyFeatures& wiphy_features,
73                          ClientInterfaceImpl* client_interface,
74                          ScanUtils* scan_utils,
75                          weak_ptr<OffloadServiceUtils> offload_service_utils)
76     : valid_(true),
77       scan_started_(false),
78       pno_scan_started_(false),
79       offload_scan_supported_(false),
80       pno_scan_running_over_offload_(false),
81       pno_scan_results_from_offload_(false),
82       interface_index_(interface_index),
83       scan_capabilities_(scan_capabilities),
84       wiphy_features_(wiphy_features),
85       client_interface_(client_interface),
86       scan_utils_(scan_utils),
87       scan_event_handler_(nullptr) {
88   // Subscribe one-shot scan result notification from kernel.
89   LOG(INFO) << "subscribe scan result for interface with index: "
90             << (int)interface_index_;
91   scan_utils_->SubscribeScanResultNotification(
92       interface_index_,
93       std::bind(&ScannerImpl::OnScanResultsReady, this, _1, _2, _3, _4));
94   // Subscribe scheduled scan result notification from kernel.
95   scan_utils_->SubscribeSchedScanResultNotification(
96       interface_index_,
97       std::bind(&ScannerImpl::OnSchedScanResultsReady,
98                 this,
99                 _1, _2));
100   std::shared_ptr<OffloadScanCallbackInterfaceImpl>
101       offload_scan_callback_interface =
102           offload_service_utils.lock()->GetOffloadScanCallbackInterface(this);
103   offload_scan_manager_ = offload_service_utils.lock()->GetOffloadScanManager(
104       offload_service_utils, offload_scan_callback_interface);
105   offload_scan_supported_ = offload_service_utils.lock()->IsOffloadScanSupported();
106 }
107 
~ScannerImpl()108 ScannerImpl::~ScannerImpl() {}
109 
Invalidate()110 void ScannerImpl::Invalidate() {
111   LOG(INFO) << "Unsubscribe scan result for interface with index: "
112             << (int)interface_index_;
113   scan_utils_->UnsubscribeScanResultNotification(interface_index_);
114   scan_utils_->UnsubscribeSchedScanResultNotification(interface_index_);
115   valid_ = false;
116 }
117 
CheckIsValid()118 bool ScannerImpl::CheckIsValid() {
119   if (!valid_) {
120     LOG(DEBUG) << "Calling on a invalid scanner object."
121                << "Underlying client interface object was destroyed.";
122   }
123   return valid_;
124 }
125 
getScanResults(vector<NativeScanResult> * out_scan_results)126 Status ScannerImpl::getScanResults(vector<NativeScanResult>* out_scan_results) {
127   if (!CheckIsValid()) {
128     return Status::ok();
129   }
130   if (!scan_utils_->GetScanResult(interface_index_, out_scan_results)) {
131     LOG(ERROR) << "Failed to get scan results via NL80211";
132   }
133   return Status::ok();
134 }
135 
getPnoScanResults(vector<NativeScanResult> * out_scan_results)136 Status ScannerImpl::getPnoScanResults(
137     vector<NativeScanResult>* out_scan_results) {
138   if (!CheckIsValid()) {
139     return Status::ok();
140   }
141   if (pno_scan_results_from_offload_) {
142     if (!offload_scan_manager_->getScanResults(out_scan_results)) {
143       LOG(ERROR) << "Failed to get scan results via Offload HAL";
144     }
145   } else {
146     if (!scan_utils_->GetScanResult(interface_index_, out_scan_results)) {
147       LOG(ERROR) << "Failed to get scan results via NL80211";
148     }
149   }
150   return Status::ok();
151 }
152 
scan(const SingleScanSettings & scan_settings,bool * out_success)153 Status ScannerImpl::scan(const SingleScanSettings& scan_settings,
154                          bool* out_success) {
155   if (!CheckIsValid()) {
156     *out_success = false;
157     return Status::ok();
158   }
159 
160   if (scan_started_) {
161     LOG(WARNING) << "Scan already started";
162   }
163   // Only request MAC address randomization when station is not associated.
164   bool request_random_mac =
165       wiphy_features_.supports_random_mac_oneshot_scan &&
166       !client_interface_->IsAssociated();
167   int scan_type = scan_settings.scan_type_;
168   if (!IsScanTypeSupported(scan_settings.scan_type_, wiphy_features_)) {
169     LOG(DEBUG) << "Ignoring scan type because device does not support it";
170     scan_type = SCAN_TYPE_DEFAULT;
171   }
172 
173   // Initialize it with an empty ssid for a wild card scan.
174   vector<vector<uint8_t>> ssids = {{}};
175 
176   vector<vector<uint8_t>> skipped_scan_ssids;
177   for (auto& network : scan_settings.hidden_networks_) {
178     if (ssids.size() + 1 > scan_capabilities_.max_num_scan_ssids) {
179       skipped_scan_ssids.emplace_back(network.ssid_);
180       continue;
181     }
182     ssids.push_back(network.ssid_);
183   }
184 
185   LogSsidList(skipped_scan_ssids, "Skip scan ssid for single scan");
186 
187   vector<uint32_t> freqs;
188   for (auto& channel : scan_settings.channel_settings_) {
189     freqs.push_back(channel.frequency_);
190   }
191 
192   int error_code = 0;
193   if (!scan_utils_->Scan(interface_index_, request_random_mac, scan_type,
194                          ssids, freqs, &error_code)) {
195     CHECK(error_code != ENODEV) << "Driver is in a bad state, restarting wificond";
196     *out_success = false;
197     return Status::ok();
198   }
199   scan_started_ = true;
200   *out_success = true;
201   return Status::ok();
202 }
203 
startPnoScan(const PnoSettings & pno_settings,bool * out_success)204 Status ScannerImpl::startPnoScan(const PnoSettings& pno_settings,
205                                  bool* out_success) {
206   pno_settings_ = pno_settings;
207   pno_scan_results_from_offload_ = false;
208   LOG(VERBOSE) << "startPnoScan";
209   if (offload_scan_supported_ && StartPnoScanOffload(pno_settings)) {
210     // scanning over offload succeeded
211     *out_success = true;
212   } else {
213     *out_success = StartPnoScanDefault(pno_settings);
214   }
215   return Status::ok();
216 }
217 
StartPnoScanOffload(const PnoSettings & pno_settings)218 bool ScannerImpl::StartPnoScanOffload(const PnoSettings& pno_settings) {
219   OffloadScanManager::ReasonCode reason_code;
220   vector<vector<uint8_t>> scan_ssids;
221   vector<vector<uint8_t>> match_ssids;
222   vector<uint8_t> match_security;
223   // Empty frequency list: scan all frequencies.
224   vector<uint32_t> freqs;
225 
226   ParsePnoSettings(pno_settings, &scan_ssids, &match_ssids, &freqs,
227                    &match_security);
228   pno_scan_running_over_offload_ = offload_scan_manager_->startScan(
229       pno_settings.interval_ms_,
230       // TODO: honor both rssi thresholds.
231       pno_settings.min_5g_rssi_, scan_ssids, match_ssids, match_security, freqs,
232       &reason_code);
233   if (pno_scan_running_over_offload_) {
234     LOG(VERBOSE) << "Pno scans requested over Offload HAL";
235     if (pno_scan_event_handler_ != nullptr) {
236       pno_scan_event_handler_->OnPnoScanOverOffloadStarted();
237     }
238   }
239   return pno_scan_running_over_offload_;
240 }
241 
ParsePnoSettings(const PnoSettings & pno_settings,vector<vector<uint8_t>> * scan_ssids,vector<vector<uint8_t>> * match_ssids,vector<uint32_t> * freqs,vector<uint8_t> * match_security)242 void ScannerImpl::ParsePnoSettings(const PnoSettings& pno_settings,
243                                    vector<vector<uint8_t>>* scan_ssids,
244                                    vector<vector<uint8_t>>* match_ssids,
245                                    vector<uint32_t>* freqs,
246                                    vector<uint8_t>* match_security) {
247   // TODO provide actionable security match parameters
248   const uint8_t kNetworkFlagsDefault = 0;
249   vector<vector<uint8_t>> skipped_scan_ssids;
250   vector<vector<uint8_t>> skipped_match_ssids;
251   std::set<int32_t> unique_frequencies;
252   int num_networks_no_freqs = 0;
253   for (auto& network : pno_settings.pno_networks_) {
254     // Add hidden network ssid.
255     if (network.is_hidden_) {
256       // TODO remove pruning for Offload Scans
257       if (scan_ssids->size() + 1 >
258           scan_capabilities_.max_num_sched_scan_ssids) {
259         skipped_scan_ssids.emplace_back(network.ssid_);
260         continue;
261       }
262       scan_ssids->push_back(network.ssid_);
263     }
264 
265     if (match_ssids->size() + 1 > scan_capabilities_.max_match_sets) {
266       skipped_match_ssids.emplace_back(network.ssid_);
267       continue;
268     }
269     match_ssids->push_back(network.ssid_);
270     match_security->push_back(kNetworkFlagsDefault);
271 
272     // build the set of unique frequencies to scan for.
273     for (const auto& frequency : network.frequencies_) {
274       unique_frequencies.insert(frequency);
275     }
276     if (network.frequencies_.empty()) {
277       num_networks_no_freqs++;
278     }
279   }
280 
281   // Also scan the default frequencies if there is frequency data passed down but more than 30% of
282   // networks don't have frequency data.
283   if (unique_frequencies.size() > 0 && num_networks_no_freqs * 100 / match_ssids->size()
284       > kPercentNetworksWithFreq) {
285     unique_frequencies.insert(std::begin(kPnoScanDefaultFreqs), std::end(kPnoScanDefaultFreqs));
286   }
287   for (const auto& frequency : unique_frequencies) {
288     freqs->push_back(frequency);
289   }
290   LogSsidList(skipped_scan_ssids, "Skip scan ssid for pno scan");
291   LogSsidList(skipped_match_ssids, "Skip match ssid for pno scan");
292 }
293 
StartPnoScanDefault(const PnoSettings & pno_settings)294 bool ScannerImpl::StartPnoScanDefault(const PnoSettings& pno_settings) {
295   if (!CheckIsValid()) {
296     return false;
297   }
298   if (pno_scan_started_) {
299     LOG(WARNING) << "Pno scan already started";
300   }
301   // An empty ssid for a wild card scan.
302   vector<vector<uint8_t>> scan_ssids = {{}};
303   vector<vector<uint8_t>> match_ssids;
304   vector<uint8_t> unused;
305   // Empty frequency list: scan all frequencies.
306   vector<uint32_t> freqs;
307 
308   ParsePnoSettings(pno_settings, &scan_ssids, &match_ssids, &freqs, &unused);
309   // Only request MAC address randomization when station is not associated.
310   bool request_random_mac = wiphy_features_.supports_random_mac_sched_scan &&
311       !client_interface_->IsAssociated();
312   // Always request a low power scan for PNO, if device supports it.
313   bool request_low_power = wiphy_features_.supports_low_power_oneshot_scan;
314 
315   bool request_sched_scan_relative_rssi = wiphy_features_.supports_ext_sched_scan_relative_rssi;
316 
317   int error_code = 0;
318   struct SchedScanReqFlags req_flags = {};
319   req_flags.request_random_mac = request_random_mac;
320   req_flags.request_low_power = request_low_power;
321   req_flags.request_sched_scan_relative_rssi = request_sched_scan_relative_rssi;
322   if (!scan_utils_->StartScheduledScan(interface_index_,
323                                        GenerateIntervalSetting(pno_settings),
324                                        pno_settings.min_2g_rssi_,
325                                        pno_settings.min_5g_rssi_,
326                                        req_flags,
327                                        scan_ssids,
328                                        match_ssids,
329                                        freqs,
330                                        &error_code)) {
331     LOG(ERROR) << "Failed to start pno scan";
332     CHECK(error_code != ENODEV) << "Driver is in a bad state, restarting wificond";
333     return false;
334   }
335   string freq_string;
336   if (freqs.empty()) {
337     freq_string = "for all supported frequencies";
338   } else {
339     freq_string = "for frequencies: ";
340     for (uint32_t f : freqs) {
341       freq_string += std::to_string(f) + ", ";
342     }
343   }
344   LOG(INFO) << "Pno scan started " << freq_string;
345   pno_scan_started_ = true;
346   return true;
347 }
348 
stopPnoScan(bool * out_success)349 Status ScannerImpl::stopPnoScan(bool* out_success) {
350   if (offload_scan_supported_ && StopPnoScanOffload()) {
351     // Pno scans over offload stopped successfully
352     *out_success = true;
353   } else {
354     // Pno scans were not requested over offload
355     *out_success = StopPnoScanDefault();
356   }
357   return Status::ok();
358 }
359 
StopPnoScanOffload()360 bool ScannerImpl::StopPnoScanOffload() {
361   OffloadScanManager::ReasonCode reason_code;
362   if (!pno_scan_running_over_offload_) {
363     return false;
364   }
365   if (!offload_scan_manager_->stopScan(&reason_code)) {
366     LOG(WARNING) << "Unable to unsubscribe to Offload scan results";
367   }
368   pno_scan_running_over_offload_ = false;
369   LOG(VERBOSE) << "Pno scans over Offload stopped";
370   return true;
371 }
372 
StopPnoScanDefault()373 bool ScannerImpl::StopPnoScanDefault() {
374   if (!CheckIsValid()) {
375     return false;
376   }
377 
378   if (!pno_scan_started_) {
379     LOG(WARNING) << "No pno scan started";
380   }
381   if (!scan_utils_->StopScheduledScan(interface_index_)) {
382     return false;
383   }
384   LOG(INFO) << "Pno scan stopped";
385   pno_scan_started_ = false;
386   return true;
387 }
388 
abortScan()389 Status ScannerImpl::abortScan() {
390   if (!CheckIsValid()) {
391     return Status::ok();
392   }
393 
394   if (!scan_started_) {
395     LOG(WARNING) << "Scan is not started. Ignore abort request";
396     return Status::ok();
397   }
398   if (!scan_utils_->AbortScan(interface_index_)) {
399     LOG(WARNING) << "Abort scan failed";
400   }
401   return Status::ok();
402 }
403 
subscribeScanEvents(const sp<IScanEvent> & handler)404 Status ScannerImpl::subscribeScanEvents(const sp<IScanEvent>& handler) {
405   if (!CheckIsValid()) {
406     return Status::ok();
407   }
408 
409   if (scan_event_handler_ != nullptr) {
410     LOG(ERROR) << "Found existing scan events subscriber."
411                << " This subscription request will unsubscribe it";
412   }
413   scan_event_handler_ = handler;
414   return Status::ok();
415 }
416 
unsubscribeScanEvents()417 Status ScannerImpl::unsubscribeScanEvents() {
418   scan_event_handler_ = nullptr;
419   return Status::ok();
420 }
421 
subscribePnoScanEvents(const sp<IPnoScanEvent> & handler)422 Status ScannerImpl::subscribePnoScanEvents(const sp<IPnoScanEvent>& handler) {
423   if (!CheckIsValid()) {
424     return Status::ok();
425   }
426 
427   if (pno_scan_event_handler_ != nullptr) {
428     LOG(ERROR) << "Found existing pno scan events subscriber."
429                << " This subscription request will unsubscribe it";
430   }
431   pno_scan_event_handler_ = handler;
432 
433   return Status::ok();
434 }
435 
unsubscribePnoScanEvents()436 Status ScannerImpl::unsubscribePnoScanEvents() {
437   pno_scan_event_handler_ = nullptr;
438   return Status::ok();
439 }
440 
OnScanResultsReady(uint32_t interface_index,bool aborted,vector<vector<uint8_t>> & ssids,vector<uint32_t> & frequencies)441 void ScannerImpl::OnScanResultsReady(uint32_t interface_index, bool aborted,
442                                      vector<vector<uint8_t>>& ssids,
443                                      vector<uint32_t>& frequencies) {
444   if (!scan_started_) {
445     LOG(INFO) << "Received external scan result notification from kernel.";
446   }
447   scan_started_ = false;
448   if (scan_event_handler_ != nullptr) {
449     // TODO: Pass other parameters back once we find framework needs them.
450     if (aborted) {
451       LOG(WARNING) << "Scan aborted";
452       scan_event_handler_->OnScanFailed();
453     } else {
454       scan_event_handler_->OnScanResultReady();
455     }
456   } else {
457     LOG(WARNING) << "No scan event handler found.";
458   }
459 }
460 
OnSchedScanResultsReady(uint32_t interface_index,bool scan_stopped)461 void ScannerImpl::OnSchedScanResultsReady(uint32_t interface_index,
462                                           bool scan_stopped) {
463   if (pno_scan_event_handler_ != nullptr) {
464     if (scan_stopped) {
465       // If |pno_scan_started_| is false.
466       // This stop notification might result from our own request.
467       // See the document for NL80211_CMD_SCHED_SCAN_STOPPED in nl80211.h.
468       if (pno_scan_started_) {
469         LOG(WARNING) << "Unexpected pno scan stopped event";
470         pno_scan_event_handler_->OnPnoScanFailed();
471       }
472       pno_scan_started_ = false;
473     } else {
474       LOG(INFO) << "Pno scan result ready event";
475       pno_scan_results_from_offload_ = false;
476       pno_scan_event_handler_->OnPnoNetworkFound();
477     }
478   }
479 }
480 
GenerateIntervalSetting(const::com::android::server::wifi::wificond::PnoSettings & pno_settings) const481 SchedScanIntervalSetting ScannerImpl::GenerateIntervalSetting(
482     const ::com::android::server::wifi::wificond::PnoSettings&
483         pno_settings) const {
484   bool support_num_scan_plans = scan_capabilities_.max_num_scan_plans >= 2;
485   bool support_scan_plan_interval =
486       scan_capabilities_.max_scan_plan_interval * 1000 >=
487           pno_settings.interval_ms_ * PnoSettings::kSlowScanIntervalMultiplier;
488   bool support_scan_plan_iterations =
489       scan_capabilities_.max_scan_plan_iterations >=
490                   PnoSettings::kFastScanIterations;
491 
492   uint32_t fast_scan_interval =
493       static_cast<uint32_t>(pno_settings.interval_ms_);
494   if (support_num_scan_plans && support_scan_plan_interval &&
495       support_scan_plan_iterations) {
496     return SchedScanIntervalSetting{
497         {{fast_scan_interval, PnoSettings::kFastScanIterations}},
498         fast_scan_interval * PnoSettings::kSlowScanIntervalMultiplier};
499   } else {
500     // Device doesn't support the provided scan plans.
501     // Specify single interval instead.
502     // In this case, the driver/firmware is expected to implement back off
503     // logic internally using |pno_settings.interval_ms_| as "fast scan"
504     // interval.
505     return SchedScanIntervalSetting{{}, fast_scan_interval};
506   }
507 }
508 
OnOffloadScanResult()509 void ScannerImpl::OnOffloadScanResult() {
510   if (!pno_scan_running_over_offload_) {
511     LOG(WARNING) << "Scan results from Offload HAL but scan not requested over "
512                     "this interface";
513     return;
514   }
515   LOG(INFO) << "Offload Scan results received";
516   pno_scan_results_from_offload_ = true;
517   if (pno_scan_event_handler_ != nullptr) {
518     pno_scan_event_handler_->OnPnoNetworkFound();
519   } else {
520     LOG(WARNING) << "No scan event handler Offload Scan result";
521   }
522 }
523 
OnOffloadError(OffloadScanCallbackInterface::AsyncErrorReason error_code)524 void ScannerImpl::OnOffloadError(
525     OffloadScanCallbackInterface::AsyncErrorReason error_code) {
526   if (!pno_scan_running_over_offload_) {
527     // Ignore irrelevant error notifications
528     LOG(WARNING) << "Offload HAL Async Error occured but Offload HAL is not "
529                     "subscribed to";
530     return;
531   }
532   LOG(ERROR) << "Offload Service Async Failure error_code=" << error_code;
533   switch (error_code) {
534     case OffloadScanCallbackInterface::AsyncErrorReason::BINDER_DEATH:
535       LOG(ERROR) << "Binder death";
536       if (pno_scan_event_handler_ != nullptr) {
537         pno_scan_event_handler_->OnPnoScanOverOffloadFailed(
538             net::wifi::IPnoScanEvent::PNO_SCAN_OVER_OFFLOAD_BINDER_FAILURE);
539       }
540       break;
541     case OffloadScanCallbackInterface::AsyncErrorReason::REMOTE_FAILURE:
542       LOG(ERROR) << "Remote failure";
543       if (pno_scan_event_handler_ != nullptr) {
544         pno_scan_event_handler_->OnPnoScanOverOffloadFailed(
545             net::wifi::IPnoScanEvent::PNO_SCAN_OVER_OFFLOAD_REMOTE_FAILURE);
546       }
547       break;
548     default:
549       LOG(WARNING) << "Invalid Error code";
550       break;
551   }
552   bool success = false;
553   // Stop scans over Offload HAL and request them over netlink
554   stopPnoScan(&success);
555   if (success) {
556     LOG(INFO) << "Pno scans stopped";
557   }
558   // Restart PNO scans over netlink interface
559   success = StartPnoScanDefault(pno_settings_);
560   if (success) {
561     LOG(INFO) << "Pno scans restarted";
562   } else {
563     LOG(ERROR) << "Unable to fall back to netlink pno scan";
564     pno_scan_event_handler_->OnPnoScanFailed();
565   }
566 }
567 
LogSsidList(vector<vector<uint8_t>> & ssid_list,string prefix)568 void ScannerImpl::LogSsidList(vector<vector<uint8_t>>& ssid_list,
569                               string prefix) {
570   if (ssid_list.empty()) {
571     return;
572   }
573   string ssid_list_string;
574   for (auto& ssid : ssid_list) {
575     ssid_list_string += string(ssid.begin(), ssid.end());
576     if (&ssid != &ssid_list.back()) {
577       ssid_list_string += ", ";
578     }
579   }
580   LOG(WARNING) << prefix << ": " << ssid_list_string;
581 }
582 
583 }  // namespace wificond
584 }  // namespace android
585