• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.wifitrackerlib;
18 
19 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
20 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED;
21 
22 import static com.android.wifitrackerlib.WifiEntry.SPEED_FAST;
23 import static com.android.wifitrackerlib.WifiEntry.SPEED_MODERATE;
24 import static com.android.wifitrackerlib.WifiEntry.SPEED_NONE;
25 import static com.android.wifitrackerlib.WifiEntry.SPEED_SLOW;
26 import static com.android.wifitrackerlib.WifiEntry.SPEED_VERY_FAST;
27 import static com.android.wifitrackerlib.WifiEntry.Speed;
28 
29 import static java.util.Comparator.comparingInt;
30 
31 import android.content.Context;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.net.NetworkCapabilities;
35 import android.net.NetworkInfo;
36 import android.net.NetworkInfo.DetailedState;
37 import android.net.NetworkKey;
38 import android.net.NetworkScoreManager;
39 import android.net.ScoredNetwork;
40 import android.net.WifiKey;
41 import android.net.wifi.ScanResult;
42 import android.net.wifi.WifiConfiguration;
43 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
44 import android.net.wifi.WifiInfo;
45 import android.net.wifi.WifiNetworkScoreCache;
46 import android.os.PersistableBundle;
47 import android.provider.Settings;
48 import android.telephony.CarrierConfigManager;
49 import android.telephony.SubscriptionInfo;
50 import android.telephony.SubscriptionManager;
51 import android.telephony.TelephonyManager;
52 import android.text.Annotation;
53 import android.text.SpannableString;
54 import android.text.SpannableStringBuilder;
55 import android.text.TextUtils;
56 import android.text.format.DateUtils;
57 import android.text.style.ClickableSpan;
58 import android.util.FeatureFlagUtils;
59 import android.view.View;
60 
61 import androidx.annotation.NonNull;
62 import androidx.annotation.Nullable;
63 
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.settingslib.HelpUtils;
66 
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.Collections;
70 import java.util.List;
71 import java.util.StringJoiner;
72 
73 /**
74  * Utility methods for WifiTrackerLib.
75  */
76 public class Utils {
77     /** Copy of the @hide Settings.Global.USE_OPEN_WIFI_PACKAGE constant. */
78     static final String SETTINGS_GLOBAL_USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
79 
80     @VisibleForTesting
81     static FeatureFlagUtilsWrapper sFeatureFlagUtilsWrapper = new FeatureFlagUtilsWrapper();
82 
83     static class FeatureFlagUtilsWrapper {
isProviderModelEnabled(Context context)84         boolean isProviderModelEnabled(Context context) {
85             return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
86         }
87     }
88 
89     private static NetworkScoreManager sNetworkScoreManager;
90 
getActiveScorerPackage(@onNull Context context)91     private static String getActiveScorerPackage(@NonNull Context context) {
92         if (sNetworkScoreManager == null) {
93             sNetworkScoreManager = context.getSystemService(NetworkScoreManager.class);
94         }
95         return sNetworkScoreManager.getActiveScorerPackage();
96     }
97 
98     // Returns the ScanResult with the best RSSI from a list of ScanResults.
99     @Nullable
getBestScanResultByLevel(@onNull List<ScanResult> scanResults)100     public static ScanResult getBestScanResultByLevel(@NonNull List<ScanResult> scanResults) {
101         if (scanResults.isEmpty()) return null;
102 
103         return Collections.max(scanResults, comparingInt(scanResult -> scanResult.level));
104     }
105 
106     // Returns a list of WifiInfo SECURITY_TYPE_* supported by a ScanResult.
107     // TODO(b/187755981): Move to shared static utils class
108     @NonNull
getSecurityTypesFromScanResult(@onNull ScanResult scanResult)109     static List<Integer> getSecurityTypesFromScanResult(@NonNull ScanResult scanResult) {
110         List<Integer> securityTypes = new ArrayList<>();
111 
112         // Open network & its upgradable types
113         if (isScanResultForOweTransitionNetwork(scanResult)) {
114             securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
115             securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
116             return securityTypes;
117         } else if (isScanResultForOweNetwork(scanResult)) {
118             securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
119             return securityTypes;
120         } else if (isScanResultForOpenNetwork(scanResult)) {
121             securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
122             return securityTypes;
123         }
124 
125         // WEP network which has no upgradable type
126         if (isScanResultForWepNetwork(scanResult)) {
127             securityTypes.add(WifiInfo.SECURITY_TYPE_WEP);
128             return securityTypes;
129         }
130 
131         // WAPI PSK network which has no upgradable type
132         if (isScanResultForWapiPskNetwork(scanResult)) {
133             securityTypes.add(WifiInfo.SECURITY_TYPE_WAPI_PSK);
134             return securityTypes;
135         }
136 
137         // WAPI CERT network which has no upgradable type
138         if (isScanResultForWapiCertNetwork(scanResult)) {
139             securityTypes.add(
140                     WifiInfo.SECURITY_TYPE_WAPI_CERT);
141             return securityTypes;
142         }
143 
144         // WPA2 personal network & its upgradable types
145         if (isScanResultForPskNetwork(scanResult)
146                 && isScanResultForSaeNetwork(scanResult)) {
147             securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
148             securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
149             return securityTypes;
150         } else if (isScanResultForPskNetwork(scanResult)) {
151             securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
152             return securityTypes;
153         } else if (isScanResultForSaeNetwork(scanResult)) {
154             securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
155             return securityTypes;
156         }
157 
158         // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
159         if (isScanResultForEapSuiteBNetwork(scanResult)) {
160             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
161         } else if (isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
162             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
163             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
164         } else if (isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
165             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
166         } else if (isScanResultForEapNetwork(scanResult)) {
167             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
168         }
169         return securityTypes;
170     }
171 
172     // Returns a list of WifiInfo SECURITY_TYPE_* supported by a WifiConfiguration
173     // TODO(b/187755473): Use new public APIs to get the security type instead of relying on the
174     //                    legacy allowedKeyManagement bitset.
getSecurityTypesFromWifiConfiguration(@onNull WifiConfiguration config)175     static List<Integer> getSecurityTypesFromWifiConfiguration(@NonNull WifiConfiguration config) {
176         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT)) {
177             return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_CERT);
178         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
179             return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_PSK);
180         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
181             return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
182         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
183             return Arrays.asList(WifiInfo.SECURITY_TYPE_OWE);
184         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
185             return Arrays.asList(WifiInfo.SECURITY_TYPE_SAE);
186         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA2_PSK)) {
187             return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
188         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
189             if (config.requirePmf
190                     && !config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.TKIP)
191                     && config.allowedProtocols.get(WifiConfiguration.Protocol.RSN)) {
192                 return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
193             } else {
194                 // WPA2 configs should also be valid for WPA3-Enterprise APs
195                 return Arrays.asList(
196                         WifiInfo.SECURITY_TYPE_EAP, WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
197             }
198         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
199             return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
200         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
201             if (config.wepKeys != null) {
202                 for (int i = 0; i < config.wepKeys.length; i++) {
203                     if (config.wepKeys[i] != null) {
204                         return Arrays.asList(WifiInfo.SECURITY_TYPE_WEP);
205                     }
206                 }
207             }
208         }
209         return Arrays.asList(WifiInfo.SECURITY_TYPE_OPEN);
210     }
211 
212     /**
213      * Returns a single WifiInfo security type from the list of multiple WifiInfo security
214      * types supported by an entry.
215      *
216      * Single security types will have a 1-to-1 mapping.
217      * Multiple security type networks will collapse to the lowest security type in the group:
218      *     - Open/OWE -> Open
219      *     - PSK/SAE -> PSK
220      *     - EAP/EAP-WPA3 -> EAP
221      */
getSingleSecurityTypeFromMultipleSecurityTypes( @onNull List<Integer> securityTypes)222     static int getSingleSecurityTypeFromMultipleSecurityTypes(
223             @NonNull List<Integer> securityTypes) {
224         if (securityTypes.size() == 1) {
225             return securityTypes.get(0);
226         } else if (securityTypes.size() == 2) {
227             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_OPEN)) {
228                 return WifiInfo.SECURITY_TYPE_OPEN;
229             }
230             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_PSK)) {
231                 return WifiInfo.SECURITY_TYPE_PSK;
232             }
233             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_EAP)) {
234                 return WifiInfo.SECURITY_TYPE_EAP;
235             }
236         }
237         return WifiInfo.SECURITY_TYPE_UNKNOWN;
238     }
239 
240     @Speed
getAverageSpeedFromScanResults(@onNull WifiNetworkScoreCache scoreCache, @NonNull List<ScanResult> scanResults)241     public static int getAverageSpeedFromScanResults(@NonNull WifiNetworkScoreCache scoreCache,
242             @NonNull List<ScanResult> scanResults) {
243         int count = 0;
244         int totalSpeed = 0;
245         for (ScanResult scanResult : scanResults) {
246             ScoredNetwork scoredNetwork = scoreCache.getScoredNetwork(scanResult);
247             if (scoredNetwork == null) {
248                 continue;
249             }
250             @Speed int speed = scoredNetwork.calculateBadge(scanResult.level);
251             if (speed != SPEED_NONE) {
252                 count++;
253                 totalSpeed += speed;
254             }
255         }
256         if (count == 0) {
257             return SPEED_NONE;
258         } else {
259             return roundToClosestSpeedEnum(totalSpeed / count);
260         }
261     }
262 
263     @Speed
getSpeedFromWifiInfo(@onNull WifiNetworkScoreCache scoreCache, @NonNull WifiInfo wifiInfo)264     public static int getSpeedFromWifiInfo(@NonNull WifiNetworkScoreCache scoreCache,
265             @NonNull WifiInfo wifiInfo) {
266         final WifiKey wifiKey;
267         try {
268             wifiKey = new WifiKey(wifiInfo.getSSID(), wifiInfo.getBSSID());
269         } catch (IllegalArgumentException e) {
270             return SPEED_NONE;
271         }
272         ScoredNetwork scoredNetwork = scoreCache.getScoredNetwork(
273                 new NetworkKey(wifiKey));
274         if (scoredNetwork == null) {
275             return SPEED_NONE;
276         }
277         return roundToClosestSpeedEnum(scoredNetwork.calculateBadge(wifiInfo.getRssi()));
278     }
279 
280     @Speed
roundToClosestSpeedEnum(int speed)281     private static int roundToClosestSpeedEnum(int speed) {
282         if (speed == SPEED_NONE) {
283             return SPEED_NONE;
284         } else if (speed < (SPEED_SLOW + SPEED_MODERATE) / 2) {
285             return SPEED_SLOW;
286         } else if (speed < (SPEED_MODERATE + SPEED_FAST) / 2) {
287             return SPEED_MODERATE;
288         } else if (speed < (SPEED_FAST + SPEED_VERY_FAST) / 2) {
289             return SPEED_FAST;
290         } else {
291             return SPEED_VERY_FAST;
292         }
293     }
294 
295     /**
296      * Get the app label for a suggestion/specifier package name, or an empty String if none exist
297      */
getAppLabel(Context context, String packageName)298     static String getAppLabel(Context context, String packageName) {
299         try {
300             String openWifiPackageName = Settings.Global.getString(context.getContentResolver(),
301                     SETTINGS_GLOBAL_USE_OPEN_WIFI_PACKAGE);
302             if (!TextUtils.isEmpty(openWifiPackageName) && TextUtils.equals(packageName,
303                     getActiveScorerPackage(context))) {
304                 packageName = openWifiPackageName;
305             }
306 
307             ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
308                     packageName,
309                     0 /* flags */);
310             return appInfo.loadLabel(context.getPackageManager()).toString();
311         } catch (PackageManager.NameNotFoundException e) {
312             return "";
313         }
314     }
315 
getConnectedDescription(Context context, WifiConfiguration wifiConfiguration, NetworkCapabilities networkCapabilities, String recommendationServiceLabel, boolean isDefaultNetwork, boolean isLowQuality)316     static String getConnectedDescription(Context context,
317             WifiConfiguration wifiConfiguration,
318             NetworkCapabilities networkCapabilities,
319             String recommendationServiceLabel,
320             boolean isDefaultNetwork,
321             boolean isLowQuality) {
322         final StringJoiner sj = new StringJoiner(context.getString(
323                 R.string.wifitrackerlib_summary_separator));
324         final boolean hideConnected =
325                 !isDefaultNetwork && sFeatureFlagUtilsWrapper.isProviderModelEnabled(context);
326 
327         if (wifiConfiguration != null) {
328             if (wifiConfiguration.fromWifiNetworkSuggestion
329                     || wifiConfiguration.fromWifiNetworkSpecifier) {
330                 // For suggestion or specifier networks to show "Connected via ..."
331                 final String suggestionOrSpecifierLabel =
332                         getSuggestionOrSpecifierLabel(context, wifiConfiguration);
333                 if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
334                     if (hideConnected) {
335                         sj.add(context.getString(R.string.wifitrackerlib_available_via_app,
336                                 suggestionOrSpecifierLabel));
337                     } else {
338                         sj.add(context.getString(R.string.wifitrackerlib_connected_via_app,
339                                 suggestionOrSpecifierLabel));
340                     }
341                 }
342             } else if (wifiConfiguration.isEphemeral() && !hideConnected) {
343                 // For ephemeral networks to show "Automatically connected via ..."
344                 if (!TextUtils.isEmpty(recommendationServiceLabel)) {
345                     sj.add(String.format(context.getString(
346                             R.string.wifitrackerlib_connected_via_network_scorer),
347                             recommendationServiceLabel));
348                 } else {
349                     sj.add(context.getString(
350                             R.string.wifitrackerlib_connected_via_network_scorer_default));
351                 }
352             }
353         }
354 
355         if (isLowQuality) {
356             sj.add(context.getString(R.string.wifi_connected_low_quality));
357         }
358 
359         // For displaying network capability info, such as captive portal or no internet
360         String networkCapabilitiesInformation =
361                 getCurrentNetworkCapabilitiesInformation(context,  networkCapabilities);
362         if (!TextUtils.isEmpty(networkCapabilitiesInformation)) {
363             sj.add(networkCapabilitiesInformation);
364         }
365 
366         // Default to "Connected" if nothing else to display
367         if (sj.length() == 0 && !hideConnected) {
368             return context.getResources().getStringArray(R.array.wifitrackerlib_wifi_status)
369                     [DetailedState.CONNECTED.ordinal()];
370         }
371 
372         return sj.toString();
373     }
374 
getConnectingDescription(Context context, NetworkInfo networkInfo)375     static String getConnectingDescription(Context context, NetworkInfo networkInfo) {
376         if (context == null || networkInfo == null) {
377             return "";
378         }
379         DetailedState detailedState = networkInfo.getDetailedState();
380         if (detailedState == null) {
381             return "";
382         }
383 
384         final String[] wifiStatusArray = context.getResources()
385                 .getStringArray(R.array.wifitrackerlib_wifi_status);
386         final int index = detailedState.ordinal();
387         return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
388     }
389 
390 
getDisconnectedDescription(Context context, WifiConfiguration wifiConfiguration, boolean forSavedNetworksPage, boolean concise)391     static String getDisconnectedDescription(Context context,
392             WifiConfiguration wifiConfiguration,
393             boolean forSavedNetworksPage,
394             boolean concise) {
395         if (context == null) {
396             return "";
397         }
398         final StringJoiner sj = new StringJoiner(context.getString(
399                 R.string.wifitrackerlib_summary_separator));
400 
401         // For "Saved", "Saved by ...", and "Available via..."
402         if (concise) {
403             sj.add(context.getString(R.string.wifitrackerlib_wifi_disconnected));
404         } else if (wifiConfiguration != null) {
405             if (forSavedNetworksPage && !wifiConfiguration.isPasspoint()) {
406                 final CharSequence appLabel = getAppLabel(context, wifiConfiguration.creatorName);
407                 if (!TextUtils.isEmpty(appLabel)) {
408                     sj.add(context.getString(R.string.wifitrackerlib_saved_network, appLabel));
409                 }
410             } else {
411                 if (wifiConfiguration.fromWifiNetworkSuggestion) {
412                     final String suggestionOrSpecifierLabel =
413                             getSuggestionOrSpecifierLabel(context, wifiConfiguration);
414                     if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
415                         sj.add(context.getString(
416                                 R.string.wifitrackerlib_available_via_app,
417                                 suggestionOrSpecifierLabel));
418                     }
419                 } else {
420                     sj.add(context.getString(R.string.wifitrackerlib_wifi_remembered));
421                 }
422             }
423         }
424 
425         // For failure messages and disabled reasons
426         final String wifiConfigFailureMessage =
427                 getWifiConfigurationFailureMessage(context, wifiConfiguration);
428         if (!TextUtils.isEmpty(wifiConfigFailureMessage)) {
429             sj.add(wifiConfigFailureMessage);
430         }
431 
432         return sj.toString();
433     }
434 
getSuggestionOrSpecifierLabel( Context context, WifiConfiguration wifiConfiguration)435     private static String getSuggestionOrSpecifierLabel(
436             Context context, WifiConfiguration wifiConfiguration) {
437         if (context == null || wifiConfiguration == null) {
438             return "";
439         }
440 
441         final String carrierName = getCarrierNameForSubId(context,
442                 getSubIdForConfig(context, wifiConfiguration));
443         if (!TextUtils.isEmpty(carrierName)) {
444             return carrierName;
445         }
446         final String suggestorLabel = getAppLabel(context, wifiConfiguration.creatorName);
447         if (!TextUtils.isEmpty(suggestorLabel)) {
448             return suggestorLabel;
449         }
450         // Fall-back to the package name in case the app label is missing
451         return wifiConfiguration.creatorName;
452     }
453 
getWifiConfigurationFailureMessage( Context context, WifiConfiguration wifiConfiguration)454     private static String getWifiConfigurationFailureMessage(
455             Context context, WifiConfiguration wifiConfiguration) {
456         if (context == null || wifiConfiguration == null) {
457             return "";
458         }
459 
460         // Check for any failure messages to display
461         if (wifiConfiguration.hasNoInternetAccess()) {
462             int messageID =
463                     wifiConfiguration.getNetworkSelectionStatus().getNetworkSelectionStatus()
464                             == NETWORK_SELECTION_PERMANENTLY_DISABLED
465                             ? R.string.wifitrackerlib_wifi_no_internet_no_reconnect
466                             : R.string.wifitrackerlib_wifi_no_internet;
467             return context.getString(messageID);
468         } else if (wifiConfiguration.getNetworkSelectionStatus().getNetworkSelectionStatus()
469                 != NETWORK_SELECTION_ENABLED) {
470             WifiConfiguration.NetworkSelectionStatus networkStatus =
471                     wifiConfiguration.getNetworkSelectionStatus();
472             switch (networkStatus.getNetworkSelectionDisableReason()) {
473                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
474                 case WifiConfiguration.NetworkSelectionStatus
475                         .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION:
476                     return context.getString(
477                             R.string.wifitrackerlib_wifi_disabled_password_failure);
478                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
479                     return context.getString(R.string.wifitrackerlib_wifi_check_password_try_again);
480                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
481                     return context.getString(R.string.wifitrackerlib_wifi_disabled_network_failure);
482                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
483                     return context.getString(R.string.wifitrackerlib_wifi_disabled_generic);
484                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT:
485                 case WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY:
486                     return context.getString(R.string.wifitrackerlib_wifi_no_internet_no_reconnect);
487                 default:
488                     break;
489             }
490         } else { // In range, not disabled.
491             switch (wifiConfiguration.getRecentFailureReason()) {
492                 case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA:
493                 case WifiConfiguration.RECENT_FAILURE_REFUSED_TEMPORARILY:
494                 case WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY:
495                     return context.getString(R.string
496                             .wifitrackerlib_wifi_ap_unable_to_handle_new_sta);
497                 case WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS:
498                     return context.getString(R.string.wifitrackerlib_wifi_poor_channel_conditions);
499                 case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED:
500                 case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED:
501                 case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED:
502                     return context.getString(R.string
503                             .wifitrackerlib_wifi_mbo_assoc_disallowed_cannot_connect);
504                 case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED:
505                     return context.getString(R.string
506                             .wifitrackerlib_wifi_mbo_assoc_disallowed_max_num_sta_associated);
507                 case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI:
508                 case WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION:
509                     return context.getString(R.string
510                             .wifitrackerlib_wifi_mbo_oce_assoc_disallowed_insufficient_rssi);
511                 case WifiConfiguration.RECENT_FAILURE_NETWORK_NOT_FOUND:
512                     return context.getString(R.string.wifitrackerlib_wifi_network_not_found);
513                 default:
514                     // do nothing
515             }
516         }
517         return "";
518     }
519 
getAutoConnectDescription(@onNull Context context, @NonNull WifiEntry wifiEntry)520     static String getAutoConnectDescription(@NonNull Context context,
521             @NonNull WifiEntry wifiEntry) {
522         if (context == null || wifiEntry == null || !wifiEntry.canSetAutoJoinEnabled()) {
523             return "";
524         }
525 
526         return wifiEntry.isAutoJoinEnabled()
527                 ? "" : context.getString(R.string.wifitrackerlib_auto_connect_disable);
528     }
529 
getMeteredDescription(@onNull Context context, @Nullable WifiEntry wifiEntry)530     static String getMeteredDescription(@NonNull Context context, @Nullable WifiEntry wifiEntry) {
531         if (context == null || wifiEntry == null) {
532             return "";
533         }
534 
535         if (!wifiEntry.canSetMeteredChoice()
536                 && wifiEntry.getMeteredChoice() != WifiEntry.METERED_CHOICE_METERED) {
537             return "";
538         }
539 
540         if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_METERED) {
541             return context.getString(R.string.wifitrackerlib_wifi_metered_label);
542         } else if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_UNMETERED) {
543             return context.getString(R.string.wifitrackerlib_wifi_unmetered_label);
544         } else { // METERED_CHOICE_AUTO
545             return wifiEntry.isMetered() ? context.getString(
546                     R.string.wifitrackerlib_wifi_metered_label) : "";
547         }
548     }
549 
getSpeedDescription(@onNull Context context, @NonNull WifiEntry wifiEntry)550     static String getSpeedDescription(@NonNull Context context, @NonNull WifiEntry wifiEntry) {
551         if (context == null || wifiEntry == null) {
552             return "";
553         }
554 
555         @Speed int speed = wifiEntry.getSpeed();
556         switch (speed) {
557             case SPEED_VERY_FAST:
558                 return context.getString(R.string.wifitrackerlib_speed_label_very_fast);
559             case SPEED_FAST:
560                 return context.getString(R.string.wifitrackerlib_speed_label_fast);
561             case SPEED_MODERATE:
562                 return context.getString(R.string.wifitrackerlib_speed_label_okay);
563             case SPEED_SLOW:
564                 return context.getString(R.string.wifitrackerlib_speed_label_slow);
565             case SPEED_NONE:
566             default:
567                 return "";
568         }
569     }
570 
getVerboseLoggingDescription(@onNull WifiEntry wifiEntry)571     static String getVerboseLoggingDescription(@NonNull WifiEntry wifiEntry) {
572         if (!BaseWifiTracker.isVerboseLoggingEnabled() || wifiEntry == null) {
573             return "";
574         }
575 
576         final StringJoiner sj = new StringJoiner(" ");
577 
578         final String wifiInfoDescription = wifiEntry.getWifiInfoDescription();
579         if (!TextUtils.isEmpty(wifiInfoDescription)) {
580             sj.add(wifiInfoDescription);
581         }
582 
583         final String networkCapabilityDescription = wifiEntry.getNetworkCapabilityDescription();
584         if (!TextUtils.isEmpty(networkCapabilityDescription)) {
585             sj.add(networkCapabilityDescription);
586         }
587 
588         final String scanResultsDescription = wifiEntry.getScanResultDescription();
589         if (!TextUtils.isEmpty(scanResultsDescription)) {
590             sj.add(scanResultsDescription);
591         }
592 
593         final String networkSelectionDescription = wifiEntry.getNetworkSelectionDescription();
594         if (!TextUtils.isEmpty(networkSelectionDescription)) {
595             sj.add(networkSelectionDescription);
596         }
597 
598         return sj.toString();
599     }
600 
getNetworkSelectionDescription(WifiConfiguration wifiConfig)601     static String getNetworkSelectionDescription(WifiConfiguration wifiConfig) {
602         if (wifiConfig == null) {
603             return "";
604         }
605 
606         StringBuilder description = new StringBuilder();
607         NetworkSelectionStatus networkSelectionStatus = wifiConfig.getNetworkSelectionStatus();
608 
609         if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED) {
610             description.append(" (" + networkSelectionStatus.getNetworkStatusString());
611             if (networkSelectionStatus.getDisableTime() > 0) {
612                 long now = System.currentTimeMillis();
613                 long elapsedSeconds = (now - networkSelectionStatus.getDisableTime()) / 1000;
614                 description.append(" " + DateUtils.formatElapsedTime(elapsedSeconds));
615             }
616             description.append(")");
617         }
618 
619         int maxNetworkSelectionDisableReason =
620                 NetworkSelectionStatus.getMaxNetworkSelectionDisableReason();
621         for (int reason = 0; reason <= maxNetworkSelectionDisableReason; reason++) {
622             int disableReasonCounter = networkSelectionStatus.getDisableReasonCounter(reason);
623             if (disableReasonCounter == 0) {
624                 continue;
625             }
626             description.append(" ")
627                     .append(NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason))
628                     .append("=")
629                     .append(disableReasonCounter);
630         }
631         return description.toString();
632     }
633 
getCurrentNetworkCapabilitiesInformation(Context context, NetworkCapabilities networkCapabilities)634     static String getCurrentNetworkCapabilitiesInformation(Context context,
635             NetworkCapabilities networkCapabilities) {
636         if (context == null || networkCapabilities == null) {
637             return "";
638         }
639 
640         if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)) {
641             return context.getString(context.getResources()
642                     .getIdentifier("network_available_sign_in", "string", "android"));
643         }
644 
645         if (networkCapabilities.hasCapability(
646                 NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
647             return context.getString(R.string.wifitrackerlib_wifi_limited_connection);
648         }
649 
650         if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
651             if (networkCapabilities.isPrivateDnsBroken()) {
652                 return context.getString(R.string.wifitrackerlib_private_dns_broken);
653             }
654             return context.getString(
655                 R.string.wifitrackerlib_wifi_connected_cannot_provide_internet);
656         }
657         return "";
658     }
659 
660     /**
661      * Returns the display string corresponding to the detailed state of the given NetworkInfo
662      */
getNetworkDetailedState(Context context, NetworkInfo networkInfo)663     static String getNetworkDetailedState(Context context, NetworkInfo networkInfo) {
664         if (context == null || networkInfo == null) {
665             return "";
666         }
667         DetailedState detailedState = networkInfo.getDetailedState();
668         if (detailedState == null) {
669             return "";
670         }
671 
672         String[] wifiStatusArray = context.getResources()
673                 .getStringArray(R.array.wifitrackerlib_wifi_status);
674         int index = detailedState.ordinal();
675         return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
676     }
677 
678     /**
679      * Check if the SIM is present for target carrier Id.
680      */
isSimPresent(@onNull Context context, int carrierId)681     static boolean isSimPresent(@NonNull Context context, int carrierId) {
682         SubscriptionManager subscriptionManager =
683                 (SubscriptionManager) context.getSystemService(
684                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
685         if (subscriptionManager == null) return false;
686         List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
687         if (subInfoList == null || subInfoList.isEmpty()) {
688             return false;
689         }
690         return subInfoList.stream()
691                 .anyMatch(info -> info.getCarrierId() == carrierId);
692     }
693 
694     /**
695      * Get the SIM carrier name for target subscription Id.
696      */
getCarrierNameForSubId(@onNull Context context, int subId)697     static @Nullable String getCarrierNameForSubId(@NonNull Context context, int subId) {
698         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
699             return null;
700         }
701         TelephonyManager telephonyManager =
702                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
703         if (telephonyManager == null) return null;
704         TelephonyManager specifiedTm = telephonyManager.createForSubscriptionId(subId);
705         if (specifiedTm == null) {
706             return null;
707         }
708         CharSequence name = specifiedTm.getSimCarrierIdName();
709         if (name == null) {
710             return null;
711         }
712         return name.toString();
713     }
714 
isSimCredential(@onNull WifiConfiguration config)715     static boolean isSimCredential(@NonNull WifiConfiguration config) {
716         return config.enterpriseConfig != null
717                 && config.enterpriseConfig.isAuthenticationSimBased();
718     }
719 
720     /**
721      * Get the best match subscription Id for target WifiConfiguration.
722      */
getSubIdForConfig(@onNull Context context, @NonNull WifiConfiguration config)723     static int getSubIdForConfig(@NonNull Context context, @NonNull WifiConfiguration config) {
724         if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
725             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
726         }
727         SubscriptionManager subscriptionManager =
728                 (SubscriptionManager) context.getSystemService(
729                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
730         if (subscriptionManager == null) {
731             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
732         }
733         List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
734         if (subInfoList == null || subInfoList.isEmpty()) {
735             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
736         }
737 
738         int matchSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
739         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
740         for (SubscriptionInfo subInfo : subInfoList) {
741             if (subInfo.getCarrierId() == config.carrierId) {
742                 matchSubId = subInfo.getSubscriptionId();
743                 if (matchSubId == dataSubId) {
744                     // Priority of Data sub is higher than non data sub.
745                     break;
746                 }
747             }
748         }
749         return matchSubId;
750     }
751 
752     /**
753      * Check if target subscription Id requires IMSI privacy protection.
754      */
isImsiPrivacyProtectionProvided(@onNull Context context, int subId)755     static boolean isImsiPrivacyProtectionProvided(@NonNull Context context, int subId) {
756         CarrierConfigManager carrierConfigManager =
757                 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
758         if (carrierConfigManager == null) {
759             return false;
760         }
761         PersistableBundle bundle = carrierConfigManager.getConfigForSubId(subId);
762         if (bundle == null) {
763             return false;
764         }
765         return (bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
766                 & TelephonyManager.KEY_TYPE_WLAN) != 0;
767     }
768 
getImsiProtectionDescription(Context context, @Nullable WifiConfiguration wifiConfig)769     static CharSequence getImsiProtectionDescription(Context context,
770             @Nullable WifiConfiguration wifiConfig) {
771         if (context == null || wifiConfig == null || !isSimCredential(wifiConfig)) {
772             return "";
773         }
774         int subId;
775         if (wifiConfig.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
776             // Config without carrierId use default data subscription.
777             subId = SubscriptionManager.getDefaultSubscriptionId();
778         } else {
779             subId = getSubIdForConfig(context, wifiConfig);
780         }
781         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
782                 || isImsiPrivacyProtectionProvided(context, subId)) {
783             return "";
784         }
785 
786         // IMSI protection is not provided, return warning message.
787         return linkifyAnnotation(context, context.getText(
788                 R.string.wifitrackerlib_imsi_protection_warning), "url",
789                 context.getString(R.string.wifitrackerlib_help_url_imsi_protection));
790     }
791 
792     /** Find the annotation of specified id in rawText and linkify it with helpUriString. */
linkifyAnnotation(Context context, CharSequence rawText, String id, String helpUriString)793     static CharSequence linkifyAnnotation(Context context, CharSequence rawText, String id,
794             String helpUriString) {
795         // Return original string when helpUriString is empty.
796         if (TextUtils.isEmpty(helpUriString)) {
797             return rawText;
798         }
799 
800         SpannableString spannableText = new SpannableString(rawText);
801         Annotation[] annotations = spannableText.getSpans(0, spannableText.length(),
802                 Annotation.class);
803 
804         for (Annotation annotation : annotations) {
805             if (TextUtils.equals(annotation.getValue(), id)) {
806                 SpannableStringBuilder builder = new SpannableStringBuilder(spannableText);
807                 ClickableSpan link = new ClickableSpan() {
808                     @Override
809                     public void onClick(View view) {
810                         view.startActivityForResult(HelpUtils.getHelpIntent(context, helpUriString,
811                                 view.getClass().getName()), 0);
812                     }
813                 };
814                 builder.setSpan(link, spannableText.getSpanStart(annotation),
815                         spannableText.getSpanEnd(annotation), spannableText.getSpanFlags(link));
816                 return builder;
817             }
818         }
819         return rawText;
820     }
821 
822     // Various utility methods copied from com.android.server.wifi.util.ScanResultUtils for
823     // extracting SecurityType from ScanResult.
824 
825     /**
826      * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
827      * This checks if the provided capabilities string contains PSK encryption type or not.
828      * TODO(b/187755981): Move to shared static utils class
829      */
isScanResultForPskNetwork(ScanResult scanResult)830     public static boolean isScanResultForPskNetwork(ScanResult scanResult) {
831         return scanResult.capabilities.contains("PSK");
832     }
833 
834     /**
835      * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not.
836      * This checks if the provided capabilities string contains PSK encryption type or not.
837      * TODO(b/187755981): Move to shared static utils class
838      */
isScanResultForWapiPskNetwork(ScanResult scanResult)839     public static boolean isScanResultForWapiPskNetwork(ScanResult scanResult) {
840         return scanResult.capabilities.contains("WAPI-PSK");
841     }
842 
843     /**
844      * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT
845      * network or not.
846      * This checks if the provided capabilities string contains PSK encryption type or not.
847      * TODO(b/187755981): Move to shared static utils class
848      */
isScanResultForWapiCertNetwork(ScanResult scanResult)849     public static boolean isScanResultForWapiCertNetwork(ScanResult scanResult) {
850         return scanResult.capabilities.contains("WAPI-CERT");
851     }
852 
853     /**
854      * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
855      * This checks these conditions:
856      * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS.
857      * - Not a WPA3 Enterprise only network.
858      * - Not a WPA3 Enterprise transition network.
859      * TODO(b/187755981): Move to shared static utils class
860      */
isScanResultForEapNetwork(ScanResult scanResult)861     public static boolean isScanResultForEapNetwork(ScanResult scanResult) {
862         return (scanResult.capabilities.contains("EAP/SHA1")
863                 || scanResult.capabilities.contains("EAP/SHA256")
864                 || scanResult.capabilities.contains("FT/EAP")
865                 || scanResult.capabilities.contains("EAP-FILS"))
866                 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
867                 && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
868     }
869 
870     // TODO(b/187755981): Move to shared static utils class
isScanResultForPmfMandatoryNetwork(ScanResult scanResult)871     private static boolean isScanResultForPmfMandatoryNetwork(ScanResult scanResult) {
872         return scanResult.capabilities.contains("[MFPR]");
873     }
874 
875     // TODO(b/187755981): Move to shared static utils class
isScanResultForPmfCapableNetwork(ScanResult scanResult)876     private static boolean isScanResultForPmfCapableNetwork(ScanResult scanResult) {
877         return scanResult.capabilities.contains("[MFPC]");
878     }
879 
880     /**
881      * Helper method to check if the provided |scanResult| corresponds to
882      * a WPA3 Enterprise transition network or not.
883      *
884      * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
885      * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
886      * - Not enable WPA1 version 1, WEP, and TKIP.
887      * - Management Frame Protection Capable is set.
888      * - Management Frame Protection Required is not set.
889      * TODO(b/187755981): Move to shared static utils class
890      */
isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult)891     public static boolean isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult) {
892         return scanResult.capabilities.contains("EAP/SHA1")
893                 && scanResult.capabilities.contains("EAP/SHA256")
894                 && scanResult.capabilities.contains("RSN")
895                 && !scanResult.capabilities.contains("WEP")
896                 && !scanResult.capabilities.contains("TKIP")
897                 && !isScanResultForPmfMandatoryNetwork(scanResult)
898                 && isScanResultForPmfCapableNetwork(scanResult);
899     }
900 
901     /**
902      * Helper method to check if the provided |scanResult| corresponds to
903      * a WPA3 Enterprise only network or not.
904      *
905      * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
906      * - Enable at least EAP/SHA256 AKM suite.
907      * - Not enable EAP/SHA1 AKM suite.
908      * - Not enable WPA1 version 1, WEP, and TKIP.
909      * - Management Frame Protection Capable is set.
910      * - Management Frame Protection Required is set.
911      * TODO(b/187755981): Move to shared static utils class
912      */
isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult)913     public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult) {
914         return scanResult.capabilities.contains("EAP/SHA256")
915                 && !scanResult.capabilities.contains("EAP/SHA1")
916                 && scanResult.capabilities.contains("RSN")
917                 && !scanResult.capabilities.contains("WEP")
918                 && !scanResult.capabilities.contains("TKIP")
919                 && isScanResultForPmfMandatoryNetwork(scanResult)
920                 && isScanResultForPmfCapableNetwork(scanResult);
921     }
922 
923 
924     /**
925      * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
926      * mode network or not.
927      * This checks if the provided capabilities comply these conditions:
928      * - Enable SUITE-B-192 AKM.
929      * - Not enable EAP/SHA1 AKM suite.
930      * - Not enable WPA1 version 1, WEP, and TKIP.
931      * - Management Frame Protection Required is set.
932      * TODO(b/187755981): Move to shared static utils class
933      */
isScanResultForEapSuiteBNetwork(ScanResult scanResult)934     public static boolean isScanResultForEapSuiteBNetwork(ScanResult scanResult) {
935         return scanResult.capabilities.contains("SUITE_B_192")
936                 && scanResult.capabilities.contains("RSN")
937                 && !scanResult.capabilities.contains("WEP")
938                 && !scanResult.capabilities.contains("TKIP")
939                 && isScanResultForPmfMandatoryNetwork(scanResult);
940     }
941 
942     /**
943      * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
944      * This checks if the provided capabilities string contains WEP encryption type or not.
945      * TODO(b/187755981): Move to shared static utils class
946      */
isScanResultForWepNetwork(ScanResult scanResult)947     public static boolean isScanResultForWepNetwork(ScanResult scanResult) {
948         return scanResult.capabilities.contains("WEP");
949     }
950 
951     /**
952      * Helper method to check if the provided |scanResult| corresponds to OWE network.
953      * This checks if the provided capabilities string contains OWE or not.
954      * TODO(b/187755981): Move to shared static utils class
955      */
isScanResultForOweNetwork(ScanResult scanResult)956     public static boolean isScanResultForOweNetwork(ScanResult scanResult) {
957         return scanResult.capabilities.contains("OWE");
958     }
959 
960     /**
961      * Helper method to check if the provided |scanResult| corresponds to OWE transition network.
962      * This checks if the provided capabilities string contains OWE_TRANSITION or not.
963      * TODO(b/187755981): Move to shared static utils class
964      */
isScanResultForOweTransitionNetwork(ScanResult scanResult)965     public static boolean isScanResultForOweTransitionNetwork(ScanResult scanResult) {
966         return scanResult.capabilities.contains("OWE_TRANSITION");
967     }
968 
969     /**
970      * Helper method to check if the provided |scanResult| corresponds to SAE network.
971      * This checks if the provided capabilities string contains SAE or not.
972      * TODO(b/187755981): Move to shared static utils class
973      */
isScanResultForSaeNetwork(ScanResult scanResult)974     public static boolean isScanResultForSaeNetwork(ScanResult scanResult) {
975         return scanResult.capabilities.contains("SAE");
976     }
977 
978     /**
979      * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
980      * network. This checks if the provided capabilities string contains both PSK and SAE or not.
981      * TODO(b/187755981): Move to shared static utils class
982      */
isScanResultForPskSaeTransitionNetwork(ScanResult scanResult)983     public static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) {
984         return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
985     }
986 
987     /**
988      *  Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
989      *  This checks if the provided capabilities string contains ? or not.
990      *  TODO(b/187755981): Move to shared static utils class
991      */
isScanResultForUnknownAkmNetwork(ScanResult scanResult)992     public static boolean isScanResultForUnknownAkmNetwork(ScanResult scanResult) {
993         return scanResult.capabilities.contains("?");
994     }
995 
996     /**
997      * Helper method to check if the provided |scanResult| corresponds to an open network or not.
998      * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
999      * EAP, or unknown encryption types or not.
1000      * TODO(b/187755981): Move to shared static utils class
1001      */
isScanResultForOpenNetwork(ScanResult scanResult)1002     public static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
1003         return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
1004                 || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult)
1005                 || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
1006                 || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
1007                 || isScanResultForWapiPskNetwork(scanResult)
1008                 || isScanResultForWapiCertNetwork(scanResult)
1009                 || isScanResultForEapSuiteBNetwork(scanResult)
1010                 || isScanResultForUnknownAkmNetwork(scanResult)));
1011     }
1012 }
1013