513 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			513 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * Copyright (C) 2016 The Android Open Source Project
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| #include "wificond/scanning/scanner_impl.h"
 | |
| 
 | |
| #include <set>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| 
 | |
| #include <android-base/logging.h>
 | |
| 
 | |
| #include "wificond/client_interface_impl.h"
 | |
| #include "wificond/scanning/scan_utils.h"
 | |
| 
 | |
| using android::binder::Status;
 | |
| using android::sp;
 | |
| using android::net::wifi::nl80211::IPnoScanEvent;
 | |
| using android::net::wifi::nl80211::IScanEvent;
 | |
| using android::net::wifi::nl80211::IWifiScannerImpl;
 | |
| using android::net::wifi::nl80211::NativeScanResult;
 | |
| using android::net::wifi::nl80211::PnoSettings;
 | |
| using android::net::wifi::nl80211::SingleScanSettings;
 | |
| 
 | |
| using std::string;
 | |
| using std::vector;
 | |
| using std::weak_ptr;
 | |
| using std::shared_ptr;
 | |
| 
 | |
| using namespace std::placeholders;
 | |
| 
 | |
| namespace {
 | |
| using android::wificond::WiphyFeatures;
 | |
| bool IsScanTypeSupported(int scan_type, const WiphyFeatures& wiphy_features) {
 | |
|   switch(scan_type) {
 | |
|     case IWifiScannerImpl::SCAN_TYPE_LOW_SPAN:
 | |
|       return wiphy_features.supports_low_span_oneshot_scan;
 | |
|     case IWifiScannerImpl::SCAN_TYPE_LOW_POWER:
 | |
|       return wiphy_features.supports_low_power_oneshot_scan;
 | |
|     case IWifiScannerImpl::SCAN_TYPE_HIGH_ACCURACY:
 | |
|       return wiphy_features.supports_high_accuracy_oneshot_scan;
 | |
|     default:
 | |
|       CHECK(0) << "Invalid scan type received: " << scan_type;
 | |
|   }
 | |
|   return {};
 | |
| }
 | |
| 
 | |
| constexpr const int kPercentNetworksWithFreq = 30;
 | |
| constexpr const int32_t kPnoScanDefaultFreqs2G[] = {2412, 2417, 2422, 2427, 2432, 2437, 2447, 2452,
 | |
|     2457, 2462};
 | |
| constexpr const int32_t kPnoScanDefaultFreqs5G[] = {5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805};
 | |
| } // namespace
 | |
| 
 | |
| namespace android {
 | |
| namespace wificond {
 | |
| 
 | |
| ScannerImpl::ScannerImpl(uint32_t interface_index,
 | |
|                          const ScanCapabilities& scan_capabilities,
 | |
|                          const WiphyFeatures& wiphy_features,
 | |
|                          ClientInterfaceImpl* client_interface,
 | |
|                          ScanUtils* scan_utils)
 | |
|     : valid_(true),
 | |
|       scan_started_(false),
 | |
|       pno_scan_started_(false),
 | |
|       nodev_counter_(0),
 | |
|       interface_index_(interface_index),
 | |
|       scan_capabilities_(scan_capabilities),
 | |
|       wiphy_features_(wiphy_features),
 | |
|       client_interface_(client_interface),
 | |
|       scan_utils_(scan_utils),
 | |
|       scan_event_handler_(nullptr) {
 | |
|   // Subscribe one-shot scan result notification from kernel.
 | |
|   LOG(INFO) << "subscribe scan result for interface with index: "
 | |
|             << (int)interface_index_;
 | |
|   scan_utils_->SubscribeScanResultNotification(
 | |
|       interface_index_,
 | |
|       std::bind(&ScannerImpl::OnScanResultsReady, this, _1, _2, _3, _4));
 | |
|   // Subscribe scheduled scan result notification from kernel.
 | |
|   scan_utils_->SubscribeSchedScanResultNotification(
 | |
|       interface_index_,
 | |
|       std::bind(&ScannerImpl::OnSchedScanResultsReady,
 | |
|                 this,
 | |
|                 _1, _2));
 | |
| }
 | |
| 
 | |
| ScannerImpl::~ScannerImpl() {}
 | |
| 
 | |
| void ScannerImpl::Invalidate() {
 | |
|   LOG(INFO) << "Unsubscribe scan result for interface with index: "
 | |
|             << (int)interface_index_;
 | |
|   scan_utils_->UnsubscribeScanResultNotification(interface_index_);
 | |
|   scan_utils_->UnsubscribeSchedScanResultNotification(interface_index_);
 | |
|   valid_ = false;
 | |
| }
 | |
| 
 | |
| bool ScannerImpl::CheckIsValid() {
 | |
|   if (!valid_) {
 | |
|     LOG(DEBUG) << "Calling on a invalid scanner object."
 | |
|                << "Underlying client interface object was destroyed.";
 | |
|   }
 | |
|   return valid_;
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::getScanResults(vector<NativeScanResult>* out_scan_results) {
 | |
|   if (!CheckIsValid()) {
 | |
|     return Status::ok();
 | |
|   }
 | |
|   if (!scan_utils_->GetScanResult(interface_index_, out_scan_results)) {
 | |
|     LOG(ERROR) << "Failed to get scan results via NL80211";
 | |
|   }
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::getPnoScanResults(
 | |
|     vector<NativeScanResult>* out_scan_results) {
 | |
|   if (!CheckIsValid()) {
 | |
|     return Status::ok();
 | |
|   }
 | |
|   if (!scan_utils_->GetScanResult(interface_index_, out_scan_results)) {
 | |
|     LOG(ERROR) << "Failed to get scan results via NL80211";
 | |
|   }
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::getMaxSsidsPerScan(int32_t* out_max_ssids_per_scan) {
 | |
|   if (!CheckIsValid()) {
 | |
|     *out_max_ssids_per_scan = 0;
 | |
|     return Status::ok();
 | |
|   }
 | |
|   *out_max_ssids_per_scan = static_cast<int32_t>(scan_capabilities_.max_num_scan_ssids);
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::scan(const SingleScanSettings& scan_settings,
 | |
|                          bool* out_success) {
 | |
|   if (!CheckIsValid()) {
 | |
|     *out_success = false;
 | |
|     return Status::ok();
 | |
|   }
 | |
| 
 | |
|   if (scan_started_) {
 | |
|     LOG(WARNING) << "Scan already started";
 | |
|   }
 | |
|   // Only request MAC address randomization when station is not associated.
 | |
|   bool request_random_mac =
 | |
|       wiphy_features_.supports_random_mac_oneshot_scan &&
 | |
|       !client_interface_->IsAssociated();
 | |
|   int scan_type = scan_settings.scan_type_;
 | |
|   if (!IsScanTypeSupported(scan_settings.scan_type_, wiphy_features_)) {
 | |
|     LOG(DEBUG) << "Ignoring scan type because device does not support it";
 | |
|     scan_type = SCAN_TYPE_DEFAULT;
 | |
|   }
 | |
| 
 | |
|   // Initialize it with an empty ssid for a wild card scan.
 | |
|   vector<vector<uint8_t>> ssids = {{}};
 | |
| 
 | |
|   vector<vector<uint8_t>> skipped_scan_ssids;
 | |
|   vector<vector<uint8_t>> skipped_long_ssids;
 | |
|   for (auto& network : scan_settings.hidden_networks_) {
 | |
|     if (ssids.size() + 1 > scan_capabilities_.max_num_scan_ssids) {
 | |
|       skipped_scan_ssids.emplace_back(network.ssid_);
 | |
|       continue;
 | |
|     }
 | |
|     if (network.ssid_.size() > 32) {
 | |
|         skipped_long_ssids.emplace_back(network.ssid_);
 | |
|         continue;
 | |
|     }
 | |
|     ssids.push_back(network.ssid_);
 | |
|   }
 | |
| 
 | |
|   LogSsidList(skipped_scan_ssids, "Skip scan ssid for single scan");
 | |
|   LogSsidList(skipped_long_ssids, "Skip too long ssid");
 | |
| 
 | |
|   vector<uint32_t> freqs;
 | |
|   for (auto& channel : scan_settings.channel_settings_) {
 | |
|     freqs.push_back(channel.frequency_);
 | |
|   }
 | |
| 
 | |
|   int error_code = 0;
 | |
|   if (!scan_utils_->Scan(interface_index_, request_random_mac, scan_type,
 | |
|                          scan_settings.enable_6ghz_rnr_, ssids, freqs, &error_code)) {
 | |
|     if (error_code == ENODEV) {
 | |
|         nodev_counter_ ++;
 | |
|         LOG(WARNING) << "Scan failed with error=nodev. counter=" << nodev_counter_;
 | |
|     }
 | |
|     CHECK(error_code != ENODEV || nodev_counter_ <= 3)
 | |
|         << "Driver is in a bad state, restarting wificond";
 | |
|     *out_success = false;
 | |
|     return Status::ok();
 | |
|   }
 | |
|   nodev_counter_ = 0;
 | |
|   scan_started_ = true;
 | |
|   *out_success = true;
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::startPnoScan(const PnoSettings& pno_settings,
 | |
|                                  bool* out_success) {
 | |
|   pno_settings_ = pno_settings;
 | |
|   LOG(VERBOSE) << "startPnoScan";
 | |
|   *out_success = StartPnoScanDefault(pno_settings);
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| void ScannerImpl::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) {
 | |
|   // TODO provide actionable security match parameters
 | |
|   const uint8_t kNetworkFlagsDefault = 0;
 | |
|   vector<vector<uint8_t>> skipped_scan_ssids;
 | |
|   vector<vector<uint8_t>> skipped_match_ssids;
 | |
|   std::set<int32_t> unique_frequencies;
 | |
|   int num_networks_no_freqs = 0;
 | |
|   // Get the list of supported frequencies
 | |
|   const auto band_2g = client_interface_->GetBandInfo().band_2g;
 | |
|   const auto band_5g = client_interface_->GetBandInfo().band_5g;
 | |
|   const auto band_dfs = client_interface_->GetBandInfo().band_dfs;
 | |
|   const auto band_6g = client_interface_->GetBandInfo().band_6g;
 | |
|   const auto band_60g = client_interface_->GetBandInfo().band_60g;
 | |
|   std::set<uint32_t> all_freqs;
 | |
|   all_freqs.insert(band_2g.begin(), band_2g.end());
 | |
|   all_freqs.insert(band_5g.begin(), band_5g.end());
 | |
|   all_freqs.insert(band_dfs.begin(), band_dfs.end());
 | |
|   all_freqs.insert(band_6g.begin(), band_6g.end());
 | |
|   all_freqs.insert(band_60g.begin(), band_60g.end());
 | |
|   for (auto& network : pno_settings.pno_networks_) {
 | |
|     // Add hidden network ssid.
 | |
|     if (network.is_hidden_) {
 | |
|       if (scan_ssids->size() + 1 >
 | |
|           scan_capabilities_.max_num_sched_scan_ssids) {
 | |
|         skipped_scan_ssids.emplace_back(network.ssid_);
 | |
|         continue;
 | |
|       }
 | |
|       scan_ssids->push_back(network.ssid_);
 | |
|     }
 | |
| 
 | |
|     if (match_ssids->size() + 1 > scan_capabilities_.max_match_sets) {
 | |
|       skipped_match_ssids.emplace_back(network.ssid_);
 | |
|       continue;
 | |
|     }
 | |
|     match_ssids->push_back(network.ssid_);
 | |
|     match_security->push_back(kNetworkFlagsDefault);
 | |
| 
 | |
|     // build the set of unique frequencies to scan for.
 | |
|     for (const auto& frequency : network.frequencies_) {
 | |
|       if (all_freqs.find(frequency) != all_freqs.end()) {
 | |
|         unique_frequencies.insert(frequency);
 | |
|       } else {
 | |
|         LOG(INFO) << "filtered out invalid frequency " << frequency;
 | |
|       }
 | |
|     }
 | |
|     if (network.frequencies_.empty()) {
 | |
|       num_networks_no_freqs++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Also scan the default frequencies if more than kPercentNetworksWithFreq of
 | |
|   // networks don't have frequency data.
 | |
|   if (num_networks_no_freqs * 100 > kPercentNetworksWithFreq * match_ssids->size()) {
 | |
|     // Filter out frequencies not supported by chip.
 | |
|     for (const auto frequency : kPnoScanDefaultFreqs2G) {
 | |
|       if (std::find(band_2g.begin(), band_2g.end(), frequency) != band_2g.end()) {
 | |
|         unique_frequencies.insert(frequency);
 | |
|       }
 | |
|     }
 | |
|     // Note: kPnoScanDefaultFreqs5G doesn't contain DFS frequencies.
 | |
|     for (const auto frequency : kPnoScanDefaultFreqs5G) {
 | |
|       if (std::find(band_5g.begin(), band_5g.end(), frequency) != band_5g.end()) {
 | |
|         unique_frequencies.insert(frequency);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   for (const auto& frequency : unique_frequencies) {
 | |
|     freqs->push_back(frequency);
 | |
|   }
 | |
|   LogSsidList(skipped_scan_ssids, "Skip scan ssid for pno scan");
 | |
|   LogSsidList(skipped_match_ssids, "Skip match ssid for pno scan");
 | |
| }
 | |
| 
 | |
| bool ScannerImpl::StartPnoScanDefault(const PnoSettings& pno_settings) {
 | |
|   if (!CheckIsValid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (pno_scan_started_) {
 | |
|     LOG(WARNING) << "Pno scan already started";
 | |
|   }
 | |
|   // An empty ssid for a wild card scan.
 | |
|   vector<vector<uint8_t>> scan_ssids = {{}};
 | |
|   vector<vector<uint8_t>> match_ssids;
 | |
|   vector<uint8_t> unused;
 | |
|   // Empty frequency list: scan all frequencies.
 | |
|   vector<uint32_t> freqs;
 | |
| 
 | |
|   ParsePnoSettings(pno_settings, &scan_ssids, &match_ssids, &freqs, &unused);
 | |
|   // Only request MAC address randomization when station is not associated.
 | |
|   bool request_random_mac = wiphy_features_.supports_random_mac_sched_scan &&
 | |
|       !client_interface_->IsAssociated();
 | |
|   // Always request a low power scan for PNO, if device supports it.
 | |
|   bool request_low_power = wiphy_features_.supports_low_power_oneshot_scan;
 | |
| 
 | |
|   bool request_sched_scan_relative_rssi = wiphy_features_.supports_ext_sched_scan_relative_rssi;
 | |
| 
 | |
|   int error_code = 0;
 | |
|   struct SchedScanReqFlags req_flags = {};
 | |
|   req_flags.request_random_mac = request_random_mac;
 | |
|   req_flags.request_low_power = request_low_power;
 | |
|   req_flags.request_sched_scan_relative_rssi = request_sched_scan_relative_rssi;
 | |
|   if (!scan_utils_->StartScheduledScan(interface_index_,
 | |
|                                        GenerateIntervalSetting(pno_settings),
 | |
|                                        pno_settings.min_2g_rssi_,
 | |
|                                        pno_settings.min_5g_rssi_,
 | |
|                                        pno_settings.min_6g_rssi_,
 | |
|                                        req_flags,
 | |
|                                        scan_ssids,
 | |
|                                        match_ssids,
 | |
|                                        freqs,
 | |
|                                        &error_code)) {
 | |
|     if (error_code == ENODEV) {
 | |
|         nodev_counter_ ++;
 | |
|         LOG(WARNING) << "Pno Scan failed with error=nodev. counter=" << nodev_counter_;
 | |
|     }
 | |
|     LOG(ERROR) << "Failed to start pno scan";
 | |
|     CHECK(error_code != ENODEV || nodev_counter_ <= 3)
 | |
|         << "Driver is in a bad state, restarting wificond";
 | |
|     return false;
 | |
|   }
 | |
|   string freq_string;
 | |
|   if (freqs.empty()) {
 | |
|     freq_string = "for all supported frequencies";
 | |
|   } else {
 | |
|     freq_string = "for frequencies: ";
 | |
|     for (uint32_t f : freqs) {
 | |
|       freq_string += std::to_string(f) + ", ";
 | |
|     }
 | |
|   }
 | |
|   LOG(INFO) << "Pno scan started " << freq_string;
 | |
|   nodev_counter_ = 0;
 | |
|   pno_scan_started_ = true;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::stopPnoScan(bool* out_success) {
 | |
|   *out_success = StopPnoScanDefault();
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| bool ScannerImpl::StopPnoScanDefault() {
 | |
|   if (!CheckIsValid()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!pno_scan_started_) {
 | |
|     LOG(WARNING) << "No pno scan started";
 | |
|   }
 | |
|   if (!scan_utils_->StopScheduledScan(interface_index_)) {
 | |
|     return false;
 | |
|   }
 | |
|   LOG(INFO) << "Pno scan stopped";
 | |
|   pno_scan_started_ = false;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::abortScan() {
 | |
|   if (!CheckIsValid()) {
 | |
|     return Status::ok();
 | |
|   }
 | |
| 
 | |
|   if (!scan_started_) {
 | |
|     LOG(WARNING) << "Scan is not started. Ignore abort request";
 | |
|     return Status::ok();
 | |
|   }
 | |
|   if (!scan_utils_->AbortScan(interface_index_)) {
 | |
|     LOG(WARNING) << "Abort scan failed";
 | |
|   }
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::subscribeScanEvents(const sp<IScanEvent>& handler) {
 | |
|   if (!CheckIsValid()) {
 | |
|     return Status::ok();
 | |
|   }
 | |
| 
 | |
|   if (scan_event_handler_ != nullptr) {
 | |
|     LOG(ERROR) << "Found existing scan events subscriber."
 | |
|                << " This subscription request will unsubscribe it";
 | |
|   }
 | |
|   scan_event_handler_ = handler;
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::unsubscribeScanEvents() {
 | |
|   scan_event_handler_ = nullptr;
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::subscribePnoScanEvents(const sp<IPnoScanEvent>& handler) {
 | |
|   if (!CheckIsValid()) {
 | |
|     return Status::ok();
 | |
|   }
 | |
| 
 | |
|   if (pno_scan_event_handler_ != nullptr) {
 | |
|     LOG(ERROR) << "Found existing pno scan events subscriber."
 | |
|                << " This subscription request will unsubscribe it";
 | |
|   }
 | |
|   pno_scan_event_handler_ = handler;
 | |
| 
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| Status ScannerImpl::unsubscribePnoScanEvents() {
 | |
|   pno_scan_event_handler_ = nullptr;
 | |
|   return Status::ok();
 | |
| }
 | |
| 
 | |
| void ScannerImpl::OnScanResultsReady(uint32_t interface_index, bool aborted,
 | |
|                                      vector<vector<uint8_t>>& ssids,
 | |
|                                      vector<uint32_t>& frequencies) {
 | |
|   if (!scan_started_) {
 | |
|     LOG(INFO) << "Received external scan result notification from kernel.";
 | |
|   }
 | |
|   scan_started_ = false;
 | |
|   if (scan_event_handler_ != nullptr) {
 | |
|     // TODO: Pass other parameters back once we find framework needs them.
 | |
|     if (aborted) {
 | |
|       LOG(WARNING) << "Scan aborted";
 | |
|       scan_event_handler_->OnScanFailed();
 | |
|     } else {
 | |
|       scan_event_handler_->OnScanResultReady();
 | |
|     }
 | |
|   } else {
 | |
|     LOG(WARNING) << "No scan event handler found.";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ScannerImpl::OnSchedScanResultsReady(uint32_t interface_index,
 | |
|                                           bool scan_stopped) {
 | |
|   if (pno_scan_event_handler_ != nullptr) {
 | |
|     if (scan_stopped) {
 | |
|       // If |pno_scan_started_| is false.
 | |
|       // This stop notification might result from our own request.
 | |
|       // See the document for NL80211_CMD_SCHED_SCAN_STOPPED in nl80211.h.
 | |
|       if (pno_scan_started_) {
 | |
|         LOG(WARNING) << "Unexpected pno scan stopped event";
 | |
|         pno_scan_event_handler_->OnPnoScanFailed();
 | |
|       }
 | |
|       pno_scan_started_ = false;
 | |
|     } else {
 | |
|       LOG(INFO) << "Pno scan result ready event";
 | |
|       pno_scan_event_handler_->OnPnoNetworkFound();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| SchedScanIntervalSetting ScannerImpl::GenerateIntervalSetting(
 | |
|     const android::net::wifi::nl80211::PnoSettings&
 | |
|         pno_settings) const {
 | |
|   bool support_num_scan_plans = scan_capabilities_.max_num_scan_plans >= 2;
 | |
|   bool support_scan_plan_interval =
 | |
|       scan_capabilities_.max_scan_plan_interval * 1000 >=
 | |
|           pno_settings.interval_ms_ * PnoSettings::kSlowScanIntervalMultiplier;
 | |
|   bool support_scan_plan_iterations =
 | |
|       scan_capabilities_.max_scan_plan_iterations >=
 | |
|                   PnoSettings::kFastScanIterations;
 | |
| 
 | |
|   uint32_t fast_scan_interval =
 | |
|       static_cast<uint32_t>(pno_settings.interval_ms_);
 | |
|   if (support_num_scan_plans && support_scan_plan_interval &&
 | |
|       support_scan_plan_iterations) {
 | |
|     return SchedScanIntervalSetting{
 | |
|         {{fast_scan_interval, PnoSettings::kFastScanIterations}},
 | |
|         fast_scan_interval * PnoSettings::kSlowScanIntervalMultiplier};
 | |
|   } else {
 | |
|     // Device doesn't support the provided scan plans.
 | |
|     // Specify single interval instead.
 | |
|     // In this case, the driver/firmware is expected to implement back off
 | |
|     // logic internally using |pno_settings.interval_ms_| as "fast scan"
 | |
|     // interval.
 | |
|     return SchedScanIntervalSetting{{}, fast_scan_interval};
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ScannerImpl::LogSsidList(vector<vector<uint8_t>>& ssid_list,
 | |
|                               string prefix) {
 | |
|   if (ssid_list.empty()) {
 | |
|     return;
 | |
|   }
 | |
|   string ssid_list_string;
 | |
|   for (auto& ssid : ssid_list) {
 | |
|     ssid_list_string += string(ssid.begin(), ssid.end());
 | |
|     if (&ssid != &ssid_list.back()) {
 | |
|       ssid_list_string += ", ";
 | |
|     }
 | |
|   }
 | |
|   LOG(WARNING) << prefix << ": " << ssid_list_string;
 | |
| }
 | |
| 
 | |
| }  // namespace wificond
 | |
| }  // namespace android
 |