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