• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package android.net.wifi.util;
18 
19 import static android.net.wifi.ScanResult.FLAG_PASSPOINT_NETWORK;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.net.MacAddress;
24 import android.net.wifi.ScanResult;
25 import android.net.wifi.SecurityParams;
26 import android.net.wifi.WifiConfiguration;
27 import android.util.Log;
28 
29 import androidx.annotation.Keep;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.List;
36 /**
37  * Scan result utility for any {@link ScanResult} related operations.
38  * Currently contains:
39  *   > Helper methods to identify the encryption of a ScanResult.
40  * @hide
41  */
42 public class ScanResultUtil {
43     private static final String TAG = "ScanResultUtil";
ScanResultUtil()44     private ScanResultUtil() { /* not constructable */ }
45 
46     /**
47      * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
48      * This checks if the provided capabilities string contains PSK encryption type or not.
49      */
isScanResultForPskNetwork(@onNull ScanResult scanResult)50     public static boolean isScanResultForPskNetwork(@NonNull ScanResult scanResult) {
51         return scanResult.capabilities.contains("PSK");
52     }
53 
54     /**
55      * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not.
56      * This checks if the provided capabilities string contains PSK encryption type or not.
57      */
isScanResultForWapiPskNetwork(@onNull ScanResult scanResult)58     public static boolean isScanResultForWapiPskNetwork(@NonNull ScanResult scanResult) {
59         return scanResult.capabilities.contains("WAPI-PSK");
60     }
61 
62     /**
63      * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT
64      * network or not.
65      * This checks if the provided capabilities string contains PSK encryption type or not.
66      */
isScanResultForWapiCertNetwork(@onNull ScanResult scanResult)67     public static boolean isScanResultForWapiCertNetwork(@NonNull ScanResult scanResult) {
68         return scanResult.capabilities.contains("WAPI-CERT");
69     }
70 
isScanResultForPmfMandatoryNetwork(@onNull ScanResult scanResult)71     private static boolean isScanResultForPmfMandatoryNetwork(@NonNull ScanResult scanResult) {
72         return scanResult.capabilities.contains("[MFPR]");
73     }
74 
isScanResultForPmfCapableNetwork(@onNull ScanResult scanResult)75     private static boolean isScanResultForPmfCapableNetwork(@NonNull ScanResult scanResult) {
76         return scanResult.capabilities.contains("[MFPC]");
77     }
78 
79     /**
80      * Helper method to check if the provided |scanResult| corresponds to a Passpoint R1/R2 network
81      * or not. Passpoint R1/R2 requirements:
82      * - Enterprise network not suite B.
83      * - Interworking bit is set.
84      * - HotSpot Release presents.
85      */
isEapScanResultForPasspointR1R2Network(@onNull ScanResult scanResult)86     public static boolean isEapScanResultForPasspointR1R2Network(@NonNull ScanResult scanResult) {
87         return scanResult.isPasspointNetwork();
88     }
89 
90     /**
91      * Helper method to check if the provided |scanResult| corresponds to a Passpoint R3 network or
92      * not. Passpoint R3 requirements:
93      * - Enterprise network not suite B.
94      * - Interworking bit is set.
95      * - HotSpot Release presents.
96      * - PMF is mandatory.
97      */
isEapScanResultForPasspointR3Network(@onNull ScanResult scanResult)98     public static boolean isEapScanResultForPasspointR3Network(@NonNull ScanResult scanResult) {
99         if (!isScanResultForPmfMandatoryNetwork(scanResult)) return false;
100 
101         return scanResult.isPasspointNetwork();
102     }
103 
104     /**
105      * Helper method to check if the provided |scanResult| corresponds to
106      * a WPA3 Enterprise transition network or not.
107      *
108      * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
109      * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
110      * - Not enable WPA1 version 1, WEP, and TKIP.
111      * - Management Frame Protection Capable is set.
112      * - Management Frame Protection Required is not set.
113      */
isScanResultForWpa3EnterpriseTransitionNetwork( @onNull ScanResult scanResult)114     public static boolean isScanResultForWpa3EnterpriseTransitionNetwork(
115             @NonNull ScanResult scanResult) {
116         return scanResult.capabilities.contains("EAP/SHA1")
117                 && scanResult.capabilities.contains("EAP/SHA256")
118                 && scanResult.capabilities.contains("RSN")
119                 && !scanResult.capabilities.contains("WEP")
120                 && !scanResult.capabilities.contains("TKIP")
121                 && !isScanResultForPmfMandatoryNetwork(scanResult)
122                 && isScanResultForPmfCapableNetwork(scanResult);
123     }
124 
125     /**
126      * Helper method to check if the provided |scanResult| corresponds to
127      * a WPA3 Enterprise only network or not.
128      *
129      * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
130      * - Enable at least EAP/SHA256 AKM suite.
131      * - Not enable EAP/SHA1 AKM suite.
132      * - Not enable WPA1 version 1, WEP, and TKIP.
133      * - Management Frame Protection Capable is set.
134      * - Management Frame Protection Required is set.
135      */
isScanResultForWpa3EnterpriseOnlyNetwork(@onNull ScanResult scanResult)136     public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(@NonNull ScanResult scanResult) {
137         return scanResult.capabilities.contains("EAP/SHA256")
138                 && !scanResult.capabilities.contains("EAP/SHA1")
139                 && scanResult.capabilities.contains("RSN")
140                 && !scanResult.capabilities.contains("WEP")
141                 && !scanResult.capabilities.contains("TKIP")
142                 && isScanResultForPmfMandatoryNetwork(scanResult)
143                 && isScanResultForPmfCapableNetwork(scanResult);
144     }
145 
146     /**
147      * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
148      * mode network or not.
149      * This checks if the provided capabilities comply these conditions:
150      * - Enable SUITE-B-192 AKM.
151      * - Not enable EAP/SHA1 AKM suite.
152      * - Not enable WPA1 version 1, WEP, and TKIP.
153      * - Management Frame Protection Required is set.
154      */
isScanResultForEapSuiteBNetwork(@onNull ScanResult scanResult)155     public static boolean isScanResultForEapSuiteBNetwork(@NonNull ScanResult scanResult) {
156         return scanResult.capabilities.contains("SUITE_B_192")
157                 && scanResult.capabilities.contains("RSN")
158                 && !scanResult.capabilities.contains("WEP")
159                 && !scanResult.capabilities.contains("TKIP")
160                 && isScanResultForPmfMandatoryNetwork(scanResult);
161     }
162 
163     /**
164      * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
165      * This checks if the provided capabilities string contains WEP encryption type or not.
166      */
isScanResultForWepNetwork(@onNull ScanResult scanResult)167     public static boolean isScanResultForWepNetwork(@NonNull ScanResult scanResult) {
168         return scanResult.capabilities.contains("WEP");
169     }
170 
171     /**
172      * Helper method to check if the provided |scanResult| corresponds to OWE network.
173      * This checks if the provided capabilities string contains OWE or not.
174      */
isScanResultForOweNetwork(@onNull ScanResult scanResult)175     public static boolean isScanResultForOweNetwork(@NonNull ScanResult scanResult) {
176         return scanResult.capabilities.contains("OWE");
177     }
178 
179     /**
180      * Helper method to check if the provided |scanResult| corresponds to OWE transition network.
181      * This checks if the provided capabilities string contains OWE_TRANSITION or not.
182      */
isScanResultForOweTransitionNetwork(@onNull ScanResult scanResult)183     public static boolean isScanResultForOweTransitionNetwork(@NonNull ScanResult scanResult) {
184         return scanResult.capabilities.contains("OWE_TRANSITION");
185     }
186 
187     /**
188      * Helper method to check if the provided |scanResult| corresponds to SAE network.
189      * This checks if the provided capabilities string contains SAE or not.
190      */
isScanResultForSaeNetwork(@onNull ScanResult scanResult)191     public static boolean isScanResultForSaeNetwork(@NonNull ScanResult scanResult) {
192         return scanResult.capabilities.contains("SAE");
193     }
194 
195     /**
196      * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
197      * network. This checks if the provided capabilities string contains both PSK and SAE or not.
198      */
isScanResultForPskSaeTransitionNetwork(@onNull ScanResult scanResult)199     public static boolean isScanResultForPskSaeTransitionNetwork(@NonNull ScanResult scanResult) {
200         return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
201     }
202 
203     /**
204      * Helper method to check if the provided |scanResult| corresponds to FILS SHA256 network.
205      * This checks if the provided capabilities string contains FILS-SHA256 or not.
206      */
isScanResultForFilsSha256Network(@onNull ScanResult scanResult)207     public static boolean isScanResultForFilsSha256Network(@NonNull ScanResult scanResult) {
208         return scanResult.capabilities.contains("FILS-SHA256");
209     }
210 
211     /**
212      * Helper method to check if the provided |scanResult| corresponds to FILS SHA384 network.
213      * This checks if the provided capabilities string contains FILS-SHA384 or not.
214      */
isScanResultForFilsSha384Network(@onNull ScanResult scanResult)215     public static boolean isScanResultForFilsSha384Network(@NonNull ScanResult scanResult) {
216         return scanResult.capabilities.contains("FILS-SHA384");
217     }
218 
219     /**
220      * Helper method to check if the provided |scanResult| corresponds to DPP network.
221      * This checks if the provided capabilities string contains DPP or not.
222      */
isScanResultForDppNetwork(@onNull ScanResult scanResult)223     public static boolean isScanResultForDppNetwork(@NonNull ScanResult scanResult) {
224         return scanResult.capabilities.contains("DPP");
225     }
226 
227     /**
228      * Helper method to check if the provided |scanResult| corresponds to only WPA-Personal network.
229      * This checks if the provided capabilities string contains WPA and not RSN.
230      */
isScanResultForWpaPersonalOnlyNetwork(@onNull ScanResult scanResult)231     public static boolean isScanResultForWpaPersonalOnlyNetwork(@NonNull ScanResult scanResult) {
232         return isScanResultForPskNetwork(scanResult) && !scanResult.capabilities.contains("RSN");
233     }
234 
235     /**
236      *  Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
237      *  This checks if the provided capabilities string contains ? or not.
238      */
isScanResultForUnknownAkmNetwork(@onNull ScanResult scanResult)239     public static boolean isScanResultForUnknownAkmNetwork(@NonNull ScanResult scanResult) {
240         return scanResult.capabilities.contains("?");
241     }
242 
243     /**
244      *  Helper method to check if the provided |scanResult| corresponds to a pure PSK network.
245      */
isScanResultForPskOnlyNetwork(@onNull ScanResult r)246     public static boolean isScanResultForPskOnlyNetwork(@NonNull ScanResult r) {
247         return ScanResultUtil.isScanResultForPskNetwork(r)
248                 && !ScanResultUtil.isScanResultForSaeNetwork(r);
249     }
250 
251     /**
252      *  Helper method to check if the provided |scanResult| corresponds to a pure SAE network.
253      */
isScanResultForSaeOnlyNetwork(@onNull ScanResult r)254     public static boolean isScanResultForSaeOnlyNetwork(@NonNull ScanResult r) {
255         return !ScanResultUtil.isScanResultForPskNetwork(r)
256                 && ScanResultUtil.isScanResultForSaeNetwork(r);
257     }
258 
259     /**
260      *  Helper method to check if the provided |scanResult| corresponds to a pure OPEN network.
261      */
isScanResultForOpenOnlyNetwork(@onNull ScanResult r)262     public static boolean isScanResultForOpenOnlyNetwork(@NonNull ScanResult r) {
263         return ScanResultUtil.isScanResultForOpenNetwork(r)
264                 && !ScanResultUtil.isScanResultForOweNetwork(r);
265     }
266 
267     /**
268      *  Helper method to check if the provided |scanResult| corresponds to a pure OWE network.
269      */
isScanResultForOweOnlyNetwork(@onNull ScanResult r)270     public static boolean isScanResultForOweOnlyNetwork(@NonNull ScanResult r) {
271         return !ScanResultUtil.isScanResultForOweTransitionNetwork(r)
272                 && ScanResultUtil.isScanResultForOweNetwork(r);
273     }
274 
275     /**
276      * Helper method to check if the provided |scanResult| corresponds to a pure WPA2 Enterprise
277      * network.
278      */
isScanResultForWpa2EnterpriseOnlyNetwork(@onNull ScanResult scanResult)279     public static boolean isScanResultForWpa2EnterpriseOnlyNetwork(@NonNull ScanResult scanResult) {
280         return (scanResult.capabilities.contains("EAP/SHA1")
281                         || scanResult.capabilities.contains("EAP/SHA256")
282                         || scanResult.capabilities.contains("FT/EAP")
283                         || scanResult.capabilities.contains("EAP-FILS"))
284                 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
285                 && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
286     }
287 
288     /**
289      * Helper method to check if the provided |scanResult| corresponds to an open network or not.
290      * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
291      * EAP, or unknown encryption types or not.
292      */
isScanResultForOpenNetwork(@onNull ScanResult scanResult)293     public static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) {
294         return (!(isScanResultForWepNetwork(scanResult)
295                 || isScanResultForPskNetwork(scanResult)
296                 || isScanResultForWpa2EnterpriseOnlyNetwork(scanResult)
297                 || isScanResultForSaeNetwork(scanResult)
298                 || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
299                 || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
300                 || isScanResultForWapiPskNetwork(scanResult)
301                 || isScanResultForWapiCertNetwork(scanResult)
302                 || isScanResultForEapSuiteBNetwork(scanResult)
303                 || isScanResultForDppNetwork(scanResult)
304                 || isScanResultForUnknownAkmNetwork(scanResult)));
305     }
306 
307     /**
308      * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in
309      * WifiConfiguration object.
310      */
311     @VisibleForTesting
createQuotedSsid(@ullable String ssid)312     public static @NonNull String createQuotedSsid(@Nullable String ssid) {
313         return "\"" + ssid + "\"";
314     }
315 
316     /**
317      * Creates a network configuration object using the provided |scanResult|.
318      */
319     @Keep
createNetworkFromScanResult( @onNull ScanResult scanResult)320     public static @Nullable WifiConfiguration createNetworkFromScanResult(
321             @NonNull ScanResult scanResult) {
322         WifiConfiguration config = new WifiConfiguration();
323         config.SSID = createQuotedSsid(scanResult.SSID);
324         List<SecurityParams> list = generateSecurityParamsListFromScanResult(scanResult);
325         if (list.isEmpty()) {
326             return null;
327         }
328         config.setSecurityParams(list);
329         return config;
330     }
331 
332     /**
333      * Generate security params from the scan result.
334      * @param scanResult the scan result to be checked.
335      * @return a list of security params. If no known security params, return an empty list.
336      */
generateSecurityParamsListFromScanResult( @onNull ScanResult scanResult)337     public static @NonNull List<SecurityParams> generateSecurityParamsListFromScanResult(
338             @NonNull ScanResult scanResult) {
339         List<SecurityParams> list = new ArrayList<>();
340 
341         // Open network & its upgradable types
342         if (ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult)) {
343             list.add(SecurityParams.createSecurityParamsBySecurityType(
344                     WifiConfiguration.SECURITY_TYPE_OPEN));
345             list.add(SecurityParams.createSecurityParamsBySecurityType(
346                     WifiConfiguration.SECURITY_TYPE_OWE));
347             return list;
348         } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) {
349             list.add(SecurityParams.createSecurityParamsBySecurityType(
350                     WifiConfiguration.SECURITY_TYPE_OWE));
351             return list;
352         } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
353             list.add(SecurityParams.createSecurityParamsBySecurityType(
354                     WifiConfiguration.SECURITY_TYPE_OPEN));
355             return list;
356         }
357 
358         // WEP network which has no upgradable type
359         if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
360             list.add(SecurityParams.createSecurityParamsBySecurityType(
361                     WifiConfiguration.SECURITY_TYPE_WEP));
362             return list;
363         }
364 
365         // WAPI PSK network which has no upgradable type
366         if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) {
367             list.add(SecurityParams.createSecurityParamsBySecurityType(
368                     WifiConfiguration.SECURITY_TYPE_WAPI_PSK));
369             return list;
370         }
371 
372         // WAPI CERT network which has no upgradable type
373         if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) {
374             list.add(SecurityParams.createSecurityParamsBySecurityType(
375                     WifiConfiguration.SECURITY_TYPE_WAPI_CERT));
376             return list;
377         }
378 
379         // WPA2 personal network & its upgradable types
380         if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
381                 && ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
382             list.add(SecurityParams.createSecurityParamsBySecurityType(
383                     WifiConfiguration.SECURITY_TYPE_PSK));
384             list.add(SecurityParams.createSecurityParamsBySecurityType(
385                     WifiConfiguration.SECURITY_TYPE_SAE));
386             return list;
387         } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
388             list.add(SecurityParams.createSecurityParamsBySecurityType(
389                     WifiConfiguration.SECURITY_TYPE_PSK));
390             return list;
391         } else if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
392             list.add(SecurityParams.createSecurityParamsBySecurityType(
393                     WifiConfiguration.SECURITY_TYPE_SAE));
394             return list;
395         } else if (ScanResultUtil.isScanResultForDppNetwork(scanResult)) {
396             list.add(SecurityParams.createSecurityParamsBySecurityType(
397                     WifiConfiguration.SECURITY_TYPE_DPP));
398             return list;
399         }
400 
401         boolean isEapNetworkAndNotSuiteB = false;
402         // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
403         if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
404             list.add(SecurityParams.createSecurityParamsBySecurityType(
405                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT));
406         } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
407             list.add(SecurityParams.createSecurityParamsBySecurityType(
408                     WifiConfiguration.SECURITY_TYPE_EAP));
409             list.add(SecurityParams.createSecurityParamsBySecurityType(
410                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
411             isEapNetworkAndNotSuiteB = true;
412         } else if (ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
413             list.add(SecurityParams.createSecurityParamsBySecurityType(
414                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
415             isEapNetworkAndNotSuiteB = true;
416         } else if (ScanResultUtil.isScanResultForWpa2EnterpriseOnlyNetwork(scanResult)) {
417             list.add(SecurityParams.createSecurityParamsBySecurityType(
418                     WifiConfiguration.SECURITY_TYPE_EAP));
419             isEapNetworkAndNotSuiteB = true;
420         }
421         if (!isEapNetworkAndNotSuiteB) {
422             return list;
423         }
424         // An Enterprise network might be a Passpoint network as well.
425         // R3 network might be also a valid R1/R2 network.
426         if (isEapScanResultForPasspointR1R2Network(scanResult)) {
427             list.add(SecurityParams.createSecurityParamsBySecurityType(
428                     WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2));
429         }
430         if (isEapScanResultForPasspointR3Network(scanResult)) {
431             list.add(SecurityParams.createSecurityParamsBySecurityType(
432                     WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3));
433         }
434         return list;
435     }
436 
437     /**
438      * Dump the provided scan results list to |pw|.
439      */
dumpScanResults(@onNull PrintWriter pw, @Nullable List<ScanResult> scanResults, long nowMs)440     public static void dumpScanResults(@NonNull PrintWriter pw,
441             @Nullable List<ScanResult> scanResults, long nowMs) {
442         if (scanResults != null && scanResults.size() != 0) {
443             pw.println("    BSSID              Frequency      RSSI           Age(sec)     SSID "
444                     + "                                Flags");
445             for (ScanResult r : scanResults) {
446                 long timeStampMs = r.timestamp / 1000;
447                 String age;
448                 if (timeStampMs <= 0) {
449                     age = "___?___";
450                 } else if (nowMs < timeStampMs) {
451                     age = "  0.000";
452                 } else if (timeStampMs < nowMs - 1000000) {
453                     age = ">1000.0";
454                 } else {
455                     age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0);
456                 }
457                 String ssid = r.SSID == null ? "" : r.SSID;
458                 String rssiInfo = "";
459                 int numRadioChainInfos = r.radioChainInfos == null ? 0 : r.radioChainInfos.length;
460                 if (numRadioChainInfos == 1) {
461                     rssiInfo = String.format("%5d(%1d:%3d)       ", r.level,
462                             r.radioChainInfos[0].id, r.radioChainInfos[0].level);
463                 } else if (numRadioChainInfos == 2) {
464                     rssiInfo = String.format("%5d(%1d:%3d/%1d:%3d)", r.level,
465                             r.radioChainInfos[0].id, r.radioChainInfos[0].level,
466                             r.radioChainInfos[1].id, r.radioChainInfos[1].level);
467                 } else {
468                     rssiInfo = String.format("%9d         ", r.level);
469                 }
470                 String capabilities = r.capabilities;
471                 if ((r.flags & FLAG_PASSPOINT_NETWORK)
472                         == FLAG_PASSPOINT_NETWORK) {
473                     capabilities += "[PASSPOINT]";
474                 }
475                 pw.printf("  %17s  %9d  %18s   %7s    %-32s  %s\n",
476                         r.BSSID,
477                         r.frequency,
478                         rssiInfo,
479                         age,
480                         String.format("%1.32s", ssid),
481                         capabilities);
482             }
483         }
484     }
485 
486     /**
487      * Check if ScanResult list is valid.
488      */
validateScanResultList(@ullable List<ScanResult> scanResults)489     public static boolean validateScanResultList(@Nullable List<ScanResult> scanResults) {
490         if (scanResults == null || scanResults.isEmpty()) {
491             Log.w(TAG, "Empty or null ScanResult list");
492             return false;
493         }
494         for (ScanResult scanResult : scanResults) {
495             if (!validate(scanResult)) {
496                 Log.w(TAG, "Invalid ScanResult: " + scanResult);
497                 return false;
498             }
499         }
500         return true;
501     }
502 
validate(@ullable ScanResult scanResult)503     private static boolean validate(@Nullable ScanResult scanResult) {
504         return scanResult != null && scanResult.SSID != null
505                 && scanResult.capabilities != null && scanResult.BSSID != null;
506     }
507 
508     /**
509      * Redact bytes from a bssid.
510      */
redactBssid(MacAddress bssid, int numRedactedOctets)511     public static String redactBssid(MacAddress bssid, int numRedactedOctets) {
512         if (bssid == null) {
513             return "";
514         }
515         StringBuilder redactedBssid = new StringBuilder();
516         byte[] bssidBytes = bssid.toByteArray();
517 
518         if (numRedactedOctets < 0 || numRedactedOctets > 6) {
519             // Reset to default if passed value is invalid.
520             numRedactedOctets = 4;
521         }
522         for (int i = 0; i < 6; i++) {
523             if (i < numRedactedOctets) {
524                 redactedBssid.append("xx");
525             } else {
526                 redactedBssid.append(String.format("%02X", bssidBytes[i]));
527             }
528             if (i != 5) {
529                 redactedBssid.append(":");
530             }
531         }
532         return redactedBssid.toString();
533     }
534 }
535